Software Architecture Patterns: A Practical Guide
Major software architecture patterns compared. Monolith, microservices, modular monolith, event-driven, serverless: trade-offs and when each makes sense.
Major software architecture patterns compared. Monolith, microservices, modular monolith, event-driven, serverless: trade-offs and when each makes sense.
Architecture is the set of fundamental decisions about how your software is structured. Get it right, and development flows smoothly for years. Get it wrong, and your team spends more time fighting the architecture than building features.
There's no universally "best" architecture. Each pattern makes trade-offs. This guide covers the major patterns, their honest trade-offs, and when each actually makes sense.
A single deployable unit containing all application functionality. One codebase, one deployment, one database. Despite the industry's obsession with microservices, monoliths remain the right choice for most applications, especially early on.
Best for: New projects, small-to-medium teams (under 10 developers), moderate complexity, MVPs and rapid prototyping. Start here unless you have clear, proven reasons not to.
The application is decomposed into small, independently deployable services. Each service owns its data, has its own database, and communicates with others via APIs or message queues.
Best for: Large teams (20+ developers), complex domains with natural service boundaries, organisations with mature DevOps and observability practices. If you don't have strong CI/CD, monitoring, and incident response, microservices will hurt more than help.
The pragmatic middle ground. A single deployable application with strictly enforced internal module boundaries. Each module owns its own database tables and communicates with others through defined interfaces, not direct database queries or shared models.
You get the modularity benefits of microservices (clear boundaries, team ownership, independent reasoning) without the distributed systems costs (network latency, eventual consistency, operational complexity).
Best for: Teams who want clear boundaries and modularity without the pain of distributed systems. Often the right architecture for medium-sized applications (5–20 developers) and a natural stepping stone toward services if and when the need is proven.
Organises code into horizontal layers: presentation (UI), business logic, data access. Each layer depends only on the layer below it. This is the classic n-tier approach.
| Layer | Responsibility |
|---|---|
| Presentation | UI rendering, API endpoints, input validation |
| Business logic | Core rules, workflows, domain operations |
| Data access | Database queries, ORM mapping, persistence |
| Database | Data storage and retrieval |
Best for: CRUD-heavy applications, teams familiar with MVC patterns, projects where the domain logic is straightforward. The default architecture for most web frameworks out of the box.
Components communicate by publishing and subscribing to events rather than calling each other directly. When something happens (order placed, user registered, payment received), an event is published. Interested components react independently.
A variant where system state is stored as a sequence of events, not as current values. To determine current state, replay all events from the beginning. This gives you complete audit history and enables powerful temporal queries ("what did the order look like at 3pm Tuesday?"). The trade-off: read models need to be built separately, and the programming model takes time to learn.
Command Query Responsibility Segregation separates read and write paths into different models. Writes go through a command model optimised for validation and business rules. Reads use query models optimised for the specific views users need. Often combined with event sourcing.
Best for: Applications with complex domain logic, strong audit requirements, or high-throughput scenarios where read and write patterns are very different. Higher complexity. Don't adopt without clear benefits that justify the learning curve.
Functions deployed and executed on-demand, with the cloud provider handling all infrastructure. No servers to provision, patch, or scale. You pay only for execution time.
Best for: Event-driven workloads (webhook handlers, file processing), variable traffic patterns, API backends for mobile apps, glue code between services. Startups who want minimal ops overhead.
Five questions that steer the decision:
Probably not, unless you have a large team (20+), a complex domain with clear service boundaries, and mature DevOps practices. Most applications that adopt microservices prematurely end up with a "distributed monolith", all the complexity of both approaches with the benefits of neither.
Yes, and you should expect to. Architecture isn't a one-time decision. It evolves as you understand the problem better and the organisation grows. A well-structured monolith with clean boundaries (modular monolith) gives you the option to extract services later without requiring it upfront.
These are complementary to the patterns above. They describe how to organise code within a service or application rather than how services relate to each other. Hexagonal architecture (ports and adapters) works well inside a monolith, a module, or a microservice.
Tell us what you're working on. We'll come back with a practical recommendation and clear next steps.