Faysal Ahmed
Chapter 5

Architecture Patterns and Design

Foundational patterns for structuring software systems.

Layered Architecture

The most widely used pattern. Layers separate concerns hierarchically:

┌─────────────────────────────────────┐
│         Presentation Layer          │
│   (UI, API endpoints, controllers)  │
├─────────────────────────────────────┤
│       Business Logic Layer          │
│   (services, domain models, rules)  │
├─────────────────────────────────────┤
│        Data Access Layer            │
│   (repositories, DAOs, ORM)         │
├─────────────────────────────────────┤
│            Database                 │
│   (SQL, NoSQL, file storage)        │
└─────────────────────────────────────┘
Figure 5.1 — Classic layered architecture. Each layer depends only on the layer below.

Pros: Simple, familiar, strong separation of concerns.

Cons: Can lead to monolithic designs, layers often leak.

Hexagonal Architecture (Ports & Adapters)

Business logic sits at the centre, isolated from external concerns. Adapters on the outside translate between the core and the outside world.

                     ┌─────────────┐
                     │   Web UI    │
                     │  (adapter)  │
                     └──────┬──────┘
                            │
┌──────────────┐    ┌───────┴────────┐    ┌──────────────┐
│  PostgreSQL  │◄───┤   Application  │◄───┤   REST API   │
│  (adapter)   │    │    Core (hex)  │    │  (adapter)   │
└──────────────┘    └───────┬────────┘    └──────────────┘
                            │
                     ┌──────┴──────┐
                     │  Message Q  │
                     │  (adapter)  │
                     └─────────────┘
Figure 5.2 — Hexagonal architecture. The core is framework-independent; adapters handle external communication.

Pros: High testability, framework independence, clear boundaries.

Cons: More initial structure, can feel abstract for simple systems.

Event-Driven Architecture

Components communicate asynchronously through events. An event bus or message broker (Kafka, RabbitMQ, SQS) routes events from producers to consumers.

┌──────────┐   order.created    ┌──────────┐
│  Order   │──────────────────►│  Kafka   │
│ Service  │                   │ (broker) │
└──────────┘                   └────┬─────┘
                                    │
                    ┌───────────────┼───────────────┐
                    │               │               │
              ┌─────▼─────┐  ┌─────▼─────┐  ┌─────▼─────┐
              │ Inventory │  │  Payment  │  │Notificat'n│
              │ Service   │  │  Service  │  │ Service   │
              └───────────┘  └───────────┘  └───────────┘
Figure 5.3 — Event-driven flow: one event triggers multiple downstream consumers.

Pros: Loose coupling, excellent scalability, good for workflows.

Cons: Eventual consistency, harder to debug, schema management overhead.

CQRS (Command Query Responsibility Segregation)

Separates read and write models. Commands change state; queries read state. Often paired with Event Sourcing.

┌──────────────┐     ┌──────────────┐
│   Command    │     │    Query     │
│    Model     │     │    Model     │
│  (writes)    │     │   (reads)    │
└──────┬───────┘     └──────┬───────┘
       │                    │
       ▼                    ▼
┌──────────────┐     ┌──────────────┐
│  Write DB    │     │   Read DB    │
│ (normalised) │◄────┤ (denormalis) │
└──────────────┘     └──────────────┘
                           ▲
                           │
                      (sync/event)
Figure 5.4 — CQRS separates the write and read paths, each optimised for its workload.

Choosing a Pattern

WhenPattern
Simple CRUD, small teamLayered
Complex domain, high testability neededHexagonal
High scale, async workflowsEvent-Driven
Different read/write volume or shapeCQRS
Table 5.1 — Pattern selection guide based on context.

Design Principles

SOLID

  • Single Responsibility
  • Open/Closed
  • Liskov Substitution
  • Interface Segregation
  • Dependency Inversion

Beyond SOLID

  • KISS — prefer simple solutions over clever ones
  • YAGNI — don’t build what you don’t need now
  • Law of Demeter — talk only to your immediate neighbours
  • Separation of Concerns — each module owns a distinct responsibility

Architecture Decision Records (ADRs)

Capture every significant decision using this template:

# Title: <short decision name>

Context

What is the problem or motivation?

Decision

What did we decide?

Consequences

What trade-offs, risks, and benefits follow?

Figure 5.5 — Standard ADR template. Keep each record focused on one decision.
ADR best practices

Store ADRs in version control alongside the code. Use a naming convention like NNNN-title.md (e.g., 0001-use-postgresql.md). Keep them short — if an ADR runs longer than one page, the decision scope may be too broad.


Next: Chapter 6 — Cloud and Infrastructure Architecture