Software architectural patterns π§±
Good architecture does not perform miracles, but it prevents disasters. In production, that is worth about as much as a well-executed miracle.
This document gives you a reasoned map of the main architectural and design patterns, grouped into macro categories. The goal is to provide shared vocabulary and a practical map to decide what to use, when, and why, without falling in love with the buzzword of the week.
Style note: you will find older names such as master/slave updated to more accurate terminology, for example primary/replica. Diagrams can stay on the whiteboard. Here we go straight to the point.
Application architectures (structural) π§©
Patterns that define the internal structure of an application and the separation of responsibilities.
Layered (n-tier) π§©
Organizes the application into layers such as presentation, application, domain, and persistence, with one-way dependencies downward.
- Pros: clear structure, high testability, easier onboarding, layer reuse.
- Cons: rigidity between layers, waterfall-like hops that add latency, temptation to punch through the layers.
- Example: enterprise CRUD app with moderate validation and business rules.
MVC (model-view-controller) π§©
Separates state handling (Model), presentation (View), and flow/interactions (Controller), a classic pattern for web UI.
- Pros: separation of responsibilities, view and controller testability, parallel work between roles.
- Cons: can become verbose; in simple domains it may add unnecessary layers.
- Example: e-commerce with catalog/cart views and controllers for user actions.
Hexagonal (ports & adapters) π§©
Puts the domain at the center; everything involving I/O such as DB, APIs, queues, and UI connects through ports (interfaces) and adapters.
- Pros: technology independence, excellent testability, adapter substitutability.
- Cons: more abstractions to manage, steeper initial curve.
- Example: payments core with multiple adapters for different PSPs.
Microkernel (plug-in) π§©
A minimal core provides base services that are extended through independent plugins.
- Pros: extensibility, fast experimentation, plugin isolation.
- Cons: plugin lifecycle management, core API compatibility.
- Example: rules engine or scheduler with plugin jobs.
Pipes & filters π§©
A chain of independent filters transforms data through a pipeline.
- Pros: composition, filter reuse, parallelization.
- Cons: serialization overhead between filters, bottlenecks.
- Example: log pipeline parse β enrich β route β store.
Modular monolith π§©
A single codebase/process organized into well-isolated modules with explicit boundaries and controlled dependencies.
- Pros: operational simplicity, local transactions, unified deployment.
- Cons: coupling risk if boundaries are not respected; it scales only up to a point.
- Example: business application with modules for orders, invoices, warehouse, and reporting.
Onion / Clean architecture π§©
Variants that, like hexagonal architecture, keep the domain at the center and push dependencies outward.
- Pros: testability, framework independence, easy infrastructure replacement.
- Cons: more abstractions, adoption curve.
- Example: service core with use cases in the application layer and repository interfaces at the edge.
MVVM (model-view-viewmodel) π§©
UI pattern that separates the View from presentation logic (ViewModel). The ViewModel mediates and adapts the Model for the View, exposing state and behavior, and is ideal for data binding.
- Pros: presentation-logic testability, reuse, and binding.
- Cons: complexity if binding is not managed with discipline.
- Example: mobile or desktop app with reactive binding between View and ViewModel.
Distributed and integration architectures π
Patterns that organize systems made of multiple components or services and their interactions.
Microservices π
An application composed of small, autonomous services that can be deployed independently, ideally each owning its own data.
- Pros: targeted scalability, faster delivery for teams, resilience to local failures.
- Cons: distributed transactions, observability, higher operational costs.
- Example: e-commerce with separate services for catalog, orders, payments, and recommendations.
SOA (service-oriented) π
More coarse-grained and standardized services, often orchestrated; historically ESB-centric, today usually lighter.
- Pros: stable contracts, service composition, governance.
- Cons: risk of a central bus becoming a bottleneck; rigidity.
- Example: enterprise services for master data, billing, and reporting shared across applications.
Event-driven π
Asynchronous communication based on events produced and consumed by decoupled components.
- Pros: decoupling, scalability, extensibility through new consumers without changing producers.
- Cons: ordering and deduplication, schema evolution, eventual consistency.
- Example: orders β event OrderPlaced β notifications/shipping/analytics.
Broker π
A mediator handles routing, transformations, and remote invocations between components.
- Pros: decoupling, centralized observability.
- Cons: latency, contention point, lock-in.
- Example: integrating legacy systems through a message broker with schema transformations.
Pub/Sub π
Producers publish to topics, consumers subscribe and receive events.
- Pros: adding consumers without affecting producers, horizontal scalability.
- Cons: end-to-end testing, ordering and partitioning management.
- Example: push and email notifications triggered from a single event.
API gateway π
A single entry point that provides routing, auth, rate limiting, aggregation, and observability toward downstream services.
- Pros: simplifies clients, centralizes policies and security.
- Cons: bottleneck risk, complex configuration.
- Example: gateway exposing public endpoints and routing to internal microservices.
Backend for Frontend (BFF) π
A dedicated backend for each front-end type such as web or mobile, adapting APIs, performance, and aggregations.
- Pros: APIs tailored to user experience, reduced over-fetching and under-fetching.
- Cons: more artifacts to maintain, duplication risk.
- Example: mobile BFF with aggregated responses and minimized payloads.
Service mesh / sidecar π
Infrastructure layer such as Envoy or Istio that pushes networking, mTLS, retries, circuit breakers, and observability into sidecars.
- Pros: consistent policies without touching application code, rich telemetry.
- Cons: operational complexity, resource overhead.
- Example: microservices with sidecar proxies for mTLS and traffic control.
Service discovery π
Dynamic resolution of service endpoints through DNS or registries so clients can find active instances.
- Pros: resilience to changes and scaling, no hardcoded endpoints.
- Cons: health-check and caching complexity.
- Example: clients resolving instances via Consul, Eureka, or DNS.
Serverless (FaaS/event-driven) π
Provider-managed functions activated by events or HTTP, with automatic scaling and pay-per-use billing.
- Pros: fast time-to-market, automatic scaling, variable costs.
- Cons: cold starts, runtime limits, harder observability and local development.
- Example: function processing queue events and updating a datastore.
Client-server π
Client requests resources or services, server processes and responds.
- Pros: centralized data, simplified security.
- Cons: single point of failure if not redundant.
- Example: mobile app interacting with REST APIs.
P2P (peer-to-peer) π
Peer nodes with no central authority, where each node can act as both client and server.
- Pros: no SPOF, scalability with nodes.
- Cons: variable security and quality of service.
- Example: file sharing or edge synchronization.
Controller-responder π
A controller coordinates writes, while responder nodes serve reads or replicas.
- Pros: isolates read load, reduces contention.
- Cons: synchronization and lag between copies.
- Example: API reading from a replica and writing to the primary.
Data, query, and persistence πΎ
Patterns focused on how to model access to and management of data.
CQRS πΎ
Clear separation between the write model (commands) and the read model (queries), so they can be optimized independently. It does not necessarily imply separate databases: separation can be logical only.
- Pros: read performance, dedicated models, independent scaling.
- Cons: eventual consistency, higher complexity.
- Example: denormalized read model for analytical dashboards.
Event sourcing πΎ
State is derived from an append-only sequence of events and rebuilt by replaying them.
- Pros: full traceability, time travel, ability to build new projections later.
- Cons: event migration, schema evolution, growing storage needs.
- Example: accounting or orders with a complete timeline.
Repository πΎ
Abstraction of data access behind domain interfaces.
- Pros: testability, separation of concerns.
- Cons: risk of hiding useful datastore-specific capabilities.
- Example:
OrderRepository.findOpenByCustomerId(...).
Sharding πΎ
Partitioning data across multiple nodes according to a key such as customerId.
- Pros: horizontal scalability, load isolation.
- Cons: hotspots if the key is unbalanced, operational complexity.
- Example: multi-tenant setup with shards per customer or region.
Primary/replica (replication) πΎ
Writes go to the primary, reads go to asynchronous or synchronous replicas.
- Pros: offloads the read path, supports failover.
- Cons: lag, temporary inconsistencies.
- Example: reporting on a replica, critical operations on the primary.
Static content hosting πΎ
Separation and delivery of static assets through CDN or edge, apart from dynamic content.
- Pros: low latency, reduced backend costs.
- Cons: cache invalidation and coherence.
- Example: images and CSS/JS on CDN; dynamic APIs from the backend.
Cache-aside πΎ
Application code reads from cache first and, on miss, loads from the database and populates the cache.
- Pros: simple, lowers latency and DB load.
- Cons: complex invalidation, stale data risk.
- Example: user profile lookups with TTL and invalidation on update.
Read-through cache πΎ
The cache intercepts reads and, on miss, loads automatically from the datastore and populates itself, transparently to the caller.
- Pros: simpler application side, automatic warm-up, reduced duplication of loading logic.
- Cons: less control over invalidation, risk of stampede without protections, dependency on cache-layer features.
- Example: product catalog automatically fetched by the cache from DB on first access.
Outbox (transactional outbox) πΎ
Write events into an outbox table within the same transaction as the main write; a separate process publishes them reliably.
- Pros: reliable event publication without losing or duplicating messages.
- Cons: dispatcher process to manage; ordering per aggregate.
- Example: when saving an order, persist the event into outbox and publish to the broker.
Lambda / Kappa architecture πΎ
Data architectures for analytics: Lambda combines batch and speed layers, Kappa uses streaming only to simplify. Note: these are system architectures more than pure persistence patterns.
- Pros: scale on large volumes, enable real-time processing.
- Cons: infrastructure complexity for Lambda, stream semantics for Kappa.
- Example: event pipeline with real-time projections and periodic batch consolidation.
Reliability, resilience, and traffic control π‘οΈ
Patterns that help prevent the whole system from collapsing when something goes wrong, because it will.
Circuit breaker π‘οΈ
Stops calls to degraded or failing dependencies to avoid avalanches of errors.
- Pros: stabilizes the system, protects resources.
- Cons: state management with open/half-open/closed, non-trivial tuning.
- Example: degrading non-essential functionality when a service is down.
Bulkhead π‘οΈ
Isolates resources such as thread pools or connections for different components, preventing one failure from saturating the whole system.
- Pros: fault containment and resource protection.
- Cons: tuning limits and resource fragmentation.
- Example: separate pools for calls to service A and service B.
Queue-based load leveling π‘οΈ
Put work into a queue to absorb spikes, with consumers processing at a sustainable pace.
- Pros: smooths spikes, protects backends, increases resilience.
- Cons: added latency, DLQ and ordering management.
- Example: queue to process orders instead of directly calling a slow service.
Retry/Timeout with backoff π‘οΈ
Configure timeouts and retries with backoff and jitter to handle transient errors without amplifying congestion.
- Pros: automatic recovery from glitches, better stability.
- Cons: if badly configured, they worsen the problem through retry storms.
- Example: HTTP calls with short timeouts and limited exponential retry.
Saga π‘οΈ
Coordinates distributed transactions through local steps and compensating actions.
- Pros: logical consistency, scalability.
- Cons: higher complexity, many error cases.
- Example: order β payment β stock reservation β shipping with appropriate rollback.
Throttling / rate limiting π‘οΈ
Traffic limitation to protect resources, SLA, and cost.
- Pros: predictable load, isolation.
- Cons: user experience impact if too aggressive.
- Example: 100 req/min for a free-tier API.
Scalability and performance β‘
Patterns designed to handle load and traffic variability.
Space-based β‘
Distributes state and load across stateless processing units, with middleware handling in-memory data, messaging, and scale.
- Pros: no central bottleneck, near-linear scalability.
- Cons: consistency complexity, harder end-to-end testing.
- Example: bidding or offer systems with sudden spikes.
Evolution and modernization π§
Strangler π§
Places a faΓ§ade between clients and legacy, migrating one feature at a time until the legacy system can quietly disappear.
- Pros: lower risk, easier rollback, measurable progress.
- Cons: double run during transition, complex routing.
- Example: moving legacy endpoints to new services with configurable proxy routing and gradual rollout.
Anti-corruption layer (ACL) π§
A layer that translates and reshapes data between systems or bounded contexts to protect your model from external impurities.
- Pros: isolates the domain from third-party models; reduces semantic coupling.
- Cons: translation code to maintain; added latency.
- Example: adapter mapping legacy DTOs into domain value objects.
Strategic modeling approaches π―
Domain-Driven Design (DDD) π―
Modeling approach that guides architectural choices through ubiquitous language, bounded contexts, aggregates, and separation of the core domain.
- Pros: business alignment, clear boundaries, expressive code.
- Cons: initial investment, risk of over-engineering on simple CRUD.
- Example: see also Domain Driven Design π§© in this section.
How to choose (in 5 moves) π§
- Clarify expected qualities: performance, scalability, time-to-market, compliance. No, you cannot optimize everything to the maximum at the same time.
- Evaluate the domain: complex and evolving? DDD plus clear boundaries. Data-intensive? CQRS or event sourcing. Heavy integration? SOA or event-driven.
- Look at the organization: small teams and few SREs? A good monolith or modular monolith. Multiple autonomous teams? Microservices, but only with mature platform and observability.
- Deal with the ecosystem: cloud-native, legacy, vendor lock-in, internal skills. The perfect architecture that the team cannot operate is not perfect.
- Move incrementally: feature flags, strangler, objective measurements. Changing course early is cheap; late is expensive.
Quick usage map π
- Want to move fast on a non-complex domain? Layered or MVC plus repository.
- Lots of reads and hard queries? CQRS, possibly with tailored projections.
- Reactivity across heterogeneous services? Event-driven plus pub/sub.
- High-value domains with stubborn rules? DDD plus hexagonal.
- Modernizing mission-critical legacy? Strangler plus characterization tests.
- Unpredictable peaks? Space-based plus caching or edge.
Compact comparison between patterns π§ͺ
Below is a compact table for some key patterns. Ratings are indicative, low/medium/high, and always need context.
| Pattern | Scalability | Complexity | Team fit |
|---|---|---|---|
| Layered | Medium | Low | Small or mixed teams |
| MVC | Medium | Medium | Teams with separate UI and backend skills |
| Hexagonal | Medium | Medium | Domain and test-oriented teams |
| Microkernel | Medium | Medium | Teams managing plugins or extensions |
| Pipes & Filters | High | Medium | Data and processing teams |
| Microservices | High | High | Multiple autonomous teams plus SRE or Platform |
| SOA | Medium | Medium | Enterprise teams with strong governance |
| Event-driven | High | High | Teams skilled in messaging or streams |
| Pub/Sub | High | Medium | Integration and real-time teams |
| Client-server | Medium | Low | Generalist teams |
| P2P | High | High | Teams experienced in networking and distributed systems |
| API gateway | N/A | Medium | Backend or platform teams |
| BFF | Medium | Medium | Collaborative frontend/backend teams |
| Service mesh/sidecar | N/A | High | SRE or platform teams |
| Service discovery | N/A | Medium | Backend or DevOps teams |
| Serverless (FaaS) | High | Medium | Teams adopting managed services |
| CQRS | High (read) | High | Teams comfortable with projections |
| Event sourcing | Medium | High | Data-intensive or audit-focused teams |
| Repository | N/A | Low | Any test-oriented team |
| Sharding | High | High | Experienced DBA or ops teams |
| Primary/replica | Medium (read) | Medium | Backend teams with DBA support |
| Static hosting | High | Low | Web/app teams using CDN |
| Cache-aside | N/A | Medium | Backend/API teams |
| Read-through cache | N/A | Medium | Backend/API teams |
| Outbox | N/A | Medium | Backend/DB/stream teams |
| Lambda/Kappa | High | High | Data/stream teams |
| Circuit breaker | N/A | Medium | Backend/platform teams |
| Bulkhead | N/A | Medium | Backend/platform teams |
| Queue-based leveling | N/A | Medium | Backend/platform teams |
| Retry/Timeout | N/A | Low | Backend teams |
| Saga | N/A | High | Experienced distributed teams |
| Throttling | N/A | Medium | API/gateway teams |
| Space-based | High | High | Performance and low-latency teams |
| Strangler | N/A | Medium | Modernization teams |
| Anti-corruption layer | N/A | Medium | DDD/integration teams |
| Modular monolith | Medium | Medium | Small and medium teams |
| Onion/Clean | Medium | Medium | Domain-centric and test-centric teams |
| MVVM | Medium | Medium | UI teams |
| DDD | N/A | Medium-High | Product and domain-centric teams |
Conclusion β
Patterns are tools, not religions. Choose the ones that maximize value in your context, observe their effects over time, and keep the system evolvable. Real seniority is knowing what not to apply.