How to Design Scalable, Maintainable Software Architecture: From Principles to Practice
This article explores how to build a robust engineering architecture by prioritizing product value, defining clear layered and DDD structures, selecting appropriate technologies, and establishing standards for exception, logging, monitoring, and team collaboration to achieve scalability, maintainability, reliability, security, and high performance.
Introduction
Software architecture must satisfy scalability, maintainability, reliability, security and high performance while being driven by product (business) value. The article presents concrete practices for achieving these qualities.
Value‑First Decision Making
When requirements are ambiguous, evaluate solutions from a commercial‑value perspective. Two common traps to avoid:
Accept everything – treating every product‑manager request as mandatory.
Technology‑driven – forcing the product to fit a clever technical implementation.
Eliminating these traps aligns development with product goals and facilitates sustainable iteration.
Architecture Design
1. Engineering Framework
Establish a baseline project skeleton that includes:
Standard directory layout and module dependency conventions.
Configuration files and code templates.
Unified interface contracts, exception coding, and log formatting.
2. Layered Architecture
Typical multi‑layer patterns (MVC, Hexagonal/Six‑sided) separate concerns and improve testability. The common layers are:
Data Access Layer – DAO/ORM (MyBatis, Hibernate) interacting with MySQL, HBase, Elasticsearch, etc.
Remote Call Layer – RPC or third‑party service adapters.
Transaction Management Layer – Cross‑cutting services such as distributed‑transaction coordinators.
Business Logic Layer – Core service orchestration.
Request Handling Layer – HTTP controllers, validation, routing.
Open Service Layer – Public APIs (REST/RPC) with gateway security and traffic control.
Presentation Layer – UI rendering (Thymeleaf, Vue, React, iOS).
Benefits: clear separation, easier maintenance, extensibility. Drawbacks: steeper learning curve, added complexity, longer initial development.
3. Domain‑Driven Design (DDD)
DDD places the domain model at the centre, isolating business concepts from technical details. The typical DDD stack consists of:
User Interface Layer – Web, RPC, MQ entry points.
Application Layer – Orchestration, validation, transaction boundaries.
Domain Layer – Entities, Value Objects, Aggregates, Domain Services, Events, Repositories, Factories.
Infrastructure Layer – Persistence, messaging, external utilities.
DDD excels for complex business logic, high maintainability and rapid iteration, but requires deep domain knowledge, cross‑team collaboration and higher technical expertise.
Technical Selection
Choosing technologies should balance business needs, team skills and long‑term costs. Evaluation dimensions:
Business requirements – functionality, performance, security, future expansion.
Technical characteristics – availability, scalability, maintainability.
Community support – active community, documentation, third‑party libraries.
Team expertise – avoid unfamiliar or overly complex tools.
Cost‑benefit – development, operation, licensing.
Risk assessment – maturity, known vulnerabilities, dependency chain.
Examples:
Object database db4o – stores objects directly, tiny DLL (~300 KB), no server needed. Suitable for embedded single‑node use, but introduces a single‑point‑of‑failure in distributed systems.
Fastjson – high‑performance JSON parser, widely used, but recent CVEs require frequent upgrades; security risk must be weighed.
JDK version alignment – Spring Boot 3 requires JDK 17; legacy systems on JDK 8 must consider migration cost versus new‑feature benefits.
Standardization & Consensus
1. Data Layering
Use distinct data transfer objects to prevent leakage of internal structures:
VO (View Object) – data for UI rendering.
DTO (Data Transfer Object) – payload for remote calls.
DO (Domain Object) – business‑level entities.
PO (Persistent Object) – ORM‑mapped persistence models.
Example: a table with 20 columns maps to a PO with 20 fields, but the front‑end only needs 10; a DTO with those 10 fields avoids exposing the full schema.
2. Object Reuse
Uncontrolled object scope leads to maintenance headaches. Mitigation strategies:
Define clear ownership and lifecycle in design documents.
Conduct regular code reviews to detect shared mutable objects.
Refactor “god objects” into smaller, purpose‑specific DTOs/VOs.
3. Exception Management
Two steps:
Capture – place try‑catch where continuation is required; otherwise catch at entry points for logging.
Handle – log stack trace, encode error codes (e.g., HTTP‑style), persist diagnostic data for later analysis.
Consistent encoding enables automated alerting and rapid root‑cause identification.
4. Log Management
Adopt a façade such as SLF4J with concrete implementations (Log4j2, Logback). Configure:
Pattern layout, output destinations, rotation policy.
Separate files per log level (INFO, WARN, ERROR).
Asynchronous logging for performance.
Periodic cleanup to avoid disk exhaustion.
Optional forwarding to ELK, Loki or other analysis pipelines.
5. Monitoring
Instrument the system for:
Resource metrics (CPU, memory, disk, network) – e.g., Prometheus + Grafana.
Log aggregation – ELK stack.
Security events – Snort/Suricata.
Business KPIs – request latency, error rate, throughput.
Distributed tracing – Zipkin, SkyWalking.
Alerting channels – email, WeChat, SMS.
Collaboration Scenarios
1. HTTP 414 (URI Too Long)
Root cause: Tomcat’s default maxHttpHeaderSize (≈8 KB). Three mitigation paths:
Increase the limit in server.xml (e.g., to 16 KB).
Support POST for large payloads instead of GET.
Trim unnecessary headers and cookies – the most sustainable fix.
Short‑term fixes (1 & 2) are easy but may affect many services; long‑term refactoring (3) reduces systemic risk.
2. Front‑end Business Logic vs. Pure Rendering
Considerations:
APP release cycles – front‑end bugs require user updates; back‑end fixes can be hot‑patched.
Clear responsibility boundaries – avoid duplicated validation.
Network overhead – moving static UI data to the front‑end reduces payload size.
Decision should minimise cost, avoid new problems and respect the product’s positioning.
Conclusion
Effective system engineering starts with a value‑first mindset, adopts a disciplined layered or DDD architecture, selects technologies based on concrete criteria, and enforces shared standards for data objects, exception handling, logging and monitoring. Practical dilemmas such as oversized GET requests or front‑end logic scope are resolved by weighing short‑term effort against long‑term maintainability, always aiming for low cost, minimal risk and alignment with product goals.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
dbaplus Community
Enterprise-level professional community for Database, BigData, and AIOps. Daily original articles, weekly online tech talks, monthly offline salons, and quarterly XCOPS&DAMS conferences—delivered by industry experts.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
