Architecture is the set of fundamental decisions about how your software is structured. Get it right, and development is smooth for years. Get it wrong, and you'll spend more time fighting your architecture than building features.
This guide covers the major architectural patterns, their trade-offs, and when each makes sense. There's no universally "best" architecture—only the right fit for your specific situation.
Monolithic Architecture
A monolith is a single deployable unit containing all application functionality. One codebase, one deployment, one database. Despite the recent trend toward microservices, monoliths remain the right choice for many applications.
Advantages
- Simplicity: One codebase to understand, one deployment to manage.
- Easy debugging: A single process to trace through.
- Efficient communication: In-process function calls, no network latency.
- Transactional consistency: Simple database transactions across all features.
- Lower operational overhead: No service discovery, API versioning, or distributed tracing.
Disadvantages
- Scaling limitations: Must scale the entire application, not individual components.
- Deployment risk: Every change requires deploying everything.
- Technology lock-in: One tech stack for the entire application.
- Team coordination: Large teams can step on each other's toes.
Best for: New projects, small teams, moderate complexity, rapid prototyping. Start with a monolith unless you have clear reasons not to.
Microservices Architecture
An application is decomposed into small, independently deployable services. Each service owns its data and communicates with others via APIs or messaging.
Advantages
- Independent deployment: Change and deploy services without affecting others.
- Technology flexibility: Different services can use different languages/frameworks.
- Targeted scaling: Scale only the services that need more capacity.
- Team autonomy: Teams can own services end-to-end.
- Fault isolation: One service failure doesn't necessarily crash everything.
Disadvantages
- Operational complexity: Many services to deploy, monitor, and manage.
- Network latency: Service-to-service calls add overhead.
- Data consistency: Distributed transactions are complex.
- Testing difficulty: Integration testing across services is challenging.
- Debugging complexity: Requests span multiple services.
Best for: Large teams, complex domains with natural service boundaries, applications requiring independent scaling, organisations with mature DevOps practices.
Modular Monolith
A middle ground: a single deployable application with strictly enforced internal module boundaries. Each module has its own database tables and communicates with others through defined interfaces—not direct database access.
Think of it as microservices benefits (modularity, clear boundaries, team ownership) without microservices costs (distributed systems complexity).
Modular Monolith Principles
- Each module has a public API that other modules use
- Modules don't access each other's database tables directly
- Modules can be extracted to services later if needed
- One deployment, but internal organisation matches potential service boundaries
Best for: Teams who want modularity without distributed system complexity. Often the right stepping stone before microservices.
Layered Architecture
Organises code into horizontal layers: presentation (UI), business logic, data access. Each layer only depends on the layer below it. This is the traditional "n-tier" approach.
| Layer | Responsibility |
|---|---|
| Presentation | User interface, API endpoints |
| Business Logic | Core application rules and workflows |
| Data Access | Database queries and persistence |
| Database | Data storage |
Best for: Applications with clear separation of concerns, traditional CRUD applications, teams familiar with this pattern.
Event-Driven Architecture
Components communicate by publishing and subscribing to events rather than calling each other directly. When something happens (order placed, user registered), an event is published. Interested components react.
Event Sourcing
A variant where state is stored as a sequence of events, not as current values. To get the current state, you replay all events. This provides complete history and enables sophisticated temporal queries.
CQRS
Command Query Responsibility Segregation separates read and write operations into different models. Writes go through the command model; reads use optimised query models. Often combined with event sourcing.
Best for: Applications with complex domain logic, audit requirements, high-throughput read/write separation needs. Higher complexity—only adopt with clear benefits.
Serverless Architecture
Functions are deployed and executed on-demand, with the cloud provider managing all infrastructure. No servers to provision or scale—you pay only for execution time.
Advantages
- No infrastructure management: Cloud provider handles scaling, patching, availability.
- Pay-per-use: No cost when not executing.
- Automatic scaling: Scales instantly with demand.
Disadvantages
- Cold starts: First invocation can be slow.
- Execution limits: Timeouts, memory limits, no local state.
- Vendor lock-in: Architectures tied to specific cloud providers.
- Debugging difficulty: Distributed execution is hard to trace.
Best for: Event-driven workloads, variable traffic patterns, glue code between services, startups wanting minimal operational overhead.
Choosing the Right Architecture
Questions to Ask
- How complex is the domain? Simple CRUD? Monolith. Complex business rules? Consider domain-driven design with a modular approach.
- How large is the team? Small teams (under 8) work well with monoliths. Larger teams benefit from service boundaries that align with team boundaries.
- What are the scaling requirements? Uniform scaling? Monolith is fine. Different components with different scaling needs? Consider services.
- What's your operational maturity? Microservices require sophisticated deployment, monitoring, and debugging capabilities.
- How much is known upfront? High uncertainty favours simpler architectures that can evolve.
General advice: Start simpler than you think you need. It's easier to extract services from a well-designed monolith than to merge poorly-conceived microservices.
Summary
Architecture isn't a one-time decision—it evolves with your understanding of the problem and your organisation's capabilities. The best architecture for year one might not be right for year five.
Focus on getting the fundamentals right: clear boundaries between components, minimal coupling, and patterns your team can execute well. The specific pattern matters less than how well it's implemented.
