Software Architecture Fundamentals
Design scalable systems - layered architecture, hexagonal design, CAP theorem, consistency patterns, and architectural decision records.
skill: Software Architecture Fundamentals category: Backend tags: [Software Architecture, System Design, Patterns, Clean Code, Scalability] readingTime: 7
Good architecture reduces the cost of change. Every architectural decision trades something - consistency for availability, simplicity for flexibility, coupling for cohesion. Name your trade-offs explicitly (in an ADR), choose deliberately, and resist the urge to add complexity before it's proven necessary.
Workflow
- Apply hexagonal architecture (ports and adapters)Hexagonal architecture isolates business logic from infrastructure - your domain code doesn't know whether it's talking to a database, an API, or a test double.
- Understand CAP theorem trade-offsDistributed systems can guarantee only two of three properties: Consistency, Availability, Partition Tolerance. Choosing a database means choosing which trade-off to accept.
- Design for the right level of couplingTight coupling makes systems hard to change. Loose coupling enables independent deployment, testing, and evolution. Know the difference between good and bad coupling.
- Write Architecture Decision RecordsADRs document architectural decisions with context and consequences - future team members understand why the system is the way it is, not just what it is.
- Apply scalability patternsUnderstand how to scale before you need to - adding scalability after the fact is expensive. Know which bottleneck you're solving before applying a pattern.
Output Format
Architecture: hexagonal ports/adapters, CAP theorem trade-off selection, interface-based loose coupling, ADR documentation, and scalability patterns (caching, CQRS).
Domain: pure logic, no imports of infrastructure
Ports: interfaces (OrderRepository, PaymentGateway)
Adapters: PostgresOrderRepository implements OrderRepository
ADR: context + decision + positive/negative consequences
Scaling: identify bottleneck → apply caching/CQRS/shardingExamples
Edge Cases & Gotchas
- Over-engineering small systems - Hexagonal architecture, CQRS, and event sourcing add complexity. A startup's initial product doesn't need all three - start with a monolith and extract when pain is felt, not anticipated.
- CAP in practice - Most databases offer tunable consistency - PostgreSQL with synchronous replicas is strongly consistent; with async replicas, it's eventually consistent. Read your database's documentation on consistency guarantees.
- ADR archaeology - Unmaintained ADRs mislead more than help. Mark outdated ADRs as "Superseded by ADR 042" rather than deleting them - history matters.
- Distributed monolith anti-pattern - Microservices with synchronous HTTP calls between every service create a distributed monolith: all the operational complexity of microservices without the independence. Use async events between services for true decoupling.
When to Break These Rules
- Use a simple layered architecture (controller → service → repository) for CRUD applications - hexagonal architecture adds indirection without proportional benefit for simple domains.
- Use event sourcing only when audit history is a core requirement - the complexity of replaying events is not justified for typical applications that just need current state.
Reference Files
https://herbertograca.com/2017/09/14/ports-adapters-architecture/https://adr.github.io/