Software Architecture Patterns
Software architecture defines the structural boundaries and communication paths of a system. Choosing a pattern is a trade-off between developer velocity, system maintainability, and operational complexity.
1. Layered (N-Tier) Architecture
The most common traditional pattern, where components are organized into horizontal layers. Each layer has a specific responsibility and typically only communicates with the layer immediately below it.
I/O Flow Diagram (Prose)
- Request Input: User sends an HTTP request to the Presentation Layer (Controller).
- Downward Call: The Controller translates the request into a method call on the Business Layer (Service).
- Transitive Dependency: The Service performs business logic and calls the Data Access Layer (Repository).
- Physical I/O: The Repository executes a SQL query against the Database.
- Upward Response: Data flows back up the stack (DB → Repo → Service → Controller → User).
Key Characteristic: High coupling. The business logic depends directly on the database implementation. If you change the database, the Service layer often requires modification.
2. Hexagonal (Ports & Adapters)
This pattern inverts the dependencies of the Layered architecture. The "Core" (Business Logic) sits at the center and defines Ports (interfaces) for everything external.
I/O Flow Diagram (Prose)
- Primary Actor: A user (or another system) interacts with a Driving Adapter (e.g., a REST API or CLI).
- Inward Translation: The Adapter converts the external signal into a call to a Driving Port (Interface implemented by the Core).
- Core Execution: The Core logic executes using only internal entities.
- Outward Port Call: When the Core needs data, it calls a Driven Port (an interface it defines, e.g.,
UserRepository). - Secondary Adapter: A Driven Adapter (e.g.,
PostgreSQLAdapter) implements that port, performing the actual physical I/O.
Key Characteristic: Dependency Inversion. The Core depends on nothing external. The Database depends on the Core's interface.
3. Event-Driven Architecture (EDA)
EDA moves away from synchronous method calls toward asynchronous message passing. Components communicate by emitting and consuming events.
I/O Flow Diagram (Prose)
- Event Source: Service A performs a local change and emits a
UserRegistered event to an Event Broker (e.g., Kafka). - Decoupled Buffer: The Broker stores the event in an append-only log. Service A is now finished; it does not wait for a response.
- Asynchronous Consumption: Service B (Emailer) and Service C (Analytics) independently poll the Broker.
- Independent Action: Service B sees the event and sends an email. Service C sees the event and updates a dashboard.
- State Consistency: The system reaches EventualConsistency as all consumers process the event log.
Key Characteristic: Temporal and Spatial Decoupling. Producers and consumers do not need to exist at the same time or know each other's location.
Comparative Summary
| Pattern | Dependency Direction | Primary Benefit | Main Drawback |
|---|
| Layered | Top → Down | Simple to understand | Hard to test without DB |
| Hexagonal | Outside → Inside | Testable, Tech-agnostic | High boilerplate |
| Event-Driven | To/From Broker | Massive Scalability | Debugging/Tracing complexity |
Further Reading