4 min read
On this page

Architectural Patterns

Comparison of monolith, microservices, and serverless architectures

Architectural patterns define the high-level structure of a software system — how components are organized, communicate, and scale.

Layered / N-Tier Architecture

Organize into horizontal layers, each with a specific responsibility.

┌─────────────────────┐
│  Presentation Layer  │  UI, API endpoints
├─────────────────────┤
│  Business Logic      │  Domain rules, services
├─────────────────────┤
│  Data Access Layer   │  Repositories, ORMs
├─────────────────────┤
│  Database            │  Storage
└─────────────────────┘

Rules: Each layer depends only on the layer below. No skipping layers (strict) or allowed skipping (relaxed).

Advantages: Separation of concerns. Easy to understand. Each layer independently testable. Standard for enterprise applications.

Disadvantages: Performance (requests traverse all layers). Tight coupling within layers. Changes often ripple across layers.

Client-Server

Centralized server provides services to multiple clients.

Thin client: Server does most processing (web browser as client). Thick client: Client does significant processing (mobile apps, desktop apps).

Modern form: REST/GraphQL APIs (server) consumed by web/mobile clients.

Microservices

Decompose the application into small, independently deployable services, each owning its own data and business logic.

┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐
│ User │  │Order │  │Inven │  │Notif │
│ Svc  │  │ Svc  │  │ Svc  │  │ Svc  │
│  DB  │  │  DB  │  │  DB  │  │Queue │
└──────┘  └──────┘  └──────┘  └──────┘
     ↕         ↕         ↕         ↕
   [API Gateway / Service Mesh]
              ↕
         [Clients]

Key principles:

  • Each service is independently deployable.
  • Each service owns its data (no shared database).
  • Services communicate via APIs (REST, gRPC) or messaging.
  • Decentralized governance (teams choose their own tech stack).

Advantages: Independent scaling. Independent deployment. Technology diversity. Fault isolation. Team autonomy.

Disadvantages: Distributed system complexity (network failures, eventual consistency). Operational overhead (monitoring, debugging). Data consistency across services is hard.

When to use: Large teams (>20 developers). Need independent scaling. Need independent deployment. Willing to invest in infrastructure.

Event-Driven Architecture

Components communicate by producing and consuming events (facts about what happened).

[Order Service] → "OrderCreated" event → [Event Bus (Kafka)]
                                              ↓
                                    [Inventory Service] → reserves stock
                                    [Notification Service] → sends email
                                    [Analytics Service] → updates dashboard

Advantages: Loose coupling (producers don't know about consumers). Easy to add new consumers. Natural audit trail. Asynchronous processing.

Disadvantages: Eventual consistency. Debugging event flows is hard. Event ordering complexities.

CQRS (Command Query Responsibility Segregation)

Separate read (Query) and write (Command) models.

Commands (writes):                    Queries (reads):
[Command Handler]                     [Query Handler]
       ↓                                    ↑
[Write Model (normalized)]   →    [Read Model (denormalized)]
       ↓                          (materialized views, caches)
   [Event Store]  → [Projections]

When to use: Very different read and write patterns. Read-heavy systems (read model optimized for queries). Complex domain with event sourcing.

Event Sourcing

Store the sequence of events that led to the current state, rather than the state itself.

Events for Account #42:
1. AccountCreated(id=42, name="Alice")
2. MoneyDeposited(amount=1000)
3. MoneyWithdrawn(amount=200)
4. MoneyDeposited(amount=500)

Current state: balance = 1000 - 200 + 500 = 1300

Advantages: Complete audit trail. Time travel (rebuild state at any point). Natural for CQRS. Enables event replay, debugging, analytics.

Disadvantages: Schema evolution (old events must still be readable). Storage growth. Rebuilding state can be slow (use snapshots).

Hexagonal Architecture (Ports and Adapters)

Isolate the core business logic from external concerns (UI, database, APIs) via ports (interfaces) and adapters (implementations).

              ┌──── Adapters ────┐
              │                  │
[REST API] ──→ Port ──→ [Core Domain] ←── Port ←── [PostgreSQL]
[CLI]      ──→ Port ──→ [  Business  ] ←── Port ←── [Redis]
[Tests]    ──→ Port ──→ [   Logic    ] ←── Port ←── [In-Memory]

Core: Pure business logic. No framework dependencies. Defines ports (traits/interfaces).

Adapters: Implement ports for specific technologies. REST adapter, database adapter, message queue adapter.

Advantages: Core is framework-independent. Easy to test (swap real adapters for in-memory). Easy to change infrastructure (swap PostgreSQL for MySQL by writing a new adapter).

Clean Architecture (Uncle Bob)

Concentric layers with the Dependency Rule: source code dependencies point inward only.

┌─────────────────────────────────────┐
│  Frameworks & Drivers (outermost)   │  Web, DB, UI
├─────────────────────────────────────┤
│  Interface Adapters                 │  Controllers, Presenters, Gateways
├─────────────────────────────────────┤
│  Use Cases (Application Logic)      │  Interactors
├─────────────────────────────────────┤
│  Entities (Domain Logic) (innermost)│  Core business rules
└─────────────────────────────────────┘

Inner layers don't know about outer layers. Entities don't know about databases. Use cases don't know about HTTP.

Pipe and Filter

Data flows through a pipeline of filters (processing steps) connected by pipes (data channels).

[Read CSV] → [Parse] → [Filter rows] → [Transform] → [Write JSON]

UNIX philosophy: cat data.csv | grep "2024" | awk -F, '{print $2}' | sort | uniq -c

Advantages: Composable. Each filter is independently testable. Easy to add/remove steps. Natural parallelism.

Examples: UNIX pipes, data processing pipelines (ETL), stream processing (Kafka Streams, Apache Flink), compiler phases, image processing.

Peer-to-Peer

Nodes are equal — each can act as both client and server.

Examples: BitTorrent (file sharing), blockchain networks, IPFS, WebRTC.

Advantages: No single point of failure. Scales with participants. Censorship-resistant.

Disadvantages: Discovery (how to find peers). Consistency. Free-riding.

Serverless

Deploy functions that are invoked by events. No server management.

[HTTP Request] → [API Gateway] → [Lambda Function] → [DynamoDB]
[S3 Upload] → [Lambda Function] → [Process Image] → [S3]
[Schedule] → [Lambda Function] → [Generate Report] → [Email]

Advantages: Pay per invocation. Auto-scaling. No infrastructure management. Fast time-to-market.

Disadvantages: Cold starts (latency). Vendor lock-in. Debugging is harder. State management complexity. Cost at scale can exceed servers.

Monolith to Microservices Migration

Strangler Fig Pattern

Gradually replace monolith functionality with microservices. Route requests to the new service; the old code slowly "strangles" as functionality moves.

Phase 1: All traffic → Monolith
Phase 2: /users → User Service, rest → Monolith
Phase 3: /users + /orders → Services, rest → Monolith
Phase 4: All traffic → Microservices (monolith retired)

Branch by Abstraction

  1. Create an abstraction (interface) for the component to extract.
  2. Migrate the monolith to use the abstraction.
  3. Create a new implementation (microservice).
  4. Switch the abstraction to use the new implementation.
  5. Remove old code.

Architecture Decision Records (ADRs)

Document architectural decisions with context, rationale, and consequences.

# ADR 0005: Use PostgreSQL for primary database

## Status: Accepted

## Context
We need a relational database for our application. Options: PostgreSQL, MySQL, SQLite.

## Decision
Use PostgreSQL.

## Rationale
- Better JSON support (JSONB) for semi-structured data
- Advanced indexing (GIN, GiST)
- Strong community and ecosystem
- Team familiarity

## Consequences
- Requires PostgreSQL deployment/management
- Team needs PostgreSQL-specific knowledge
- Migration from SQLite (current) required

Applications in CS

  • Web applications: Layered or hexagonal for monoliths. Microservices for large-scale. Serverless for event-driven.
  • Data pipelines: Pipe and filter for ETL. Event-driven for real-time processing.
  • Mobile apps: Clean architecture (separation of UI from business logic). MVVM/MVP patterns.
  • Gaming: Event-driven for game events. ECS (Entity Component System) for game objects.
  • Enterprise: CQRS + Event Sourcing for complex domains. Microservices for organizational scaling.