Starphix
Software Engineering / Sources
The Lab / Sources / Software Engineering / Architecture/Architectural-Styles.md

Architectural Styles

Comparison of major architectural patterns for structuring software systems. Each style manages dependencies differently and suits different contexts.


Layered Architecture (Classic)

┌─────────────────────┐
│   Presentation      │  ← UI, controllers, API endpoints
├─────────────────────┤
│   Application       │  ← Use cases, orchestration, services
├─────────────────────┤
│   Domain            │  ← Business logic, entities, rules
├─────────────────────┤
│   Infrastructure    │  ← DB, external APIs, frameworks
└─────────────────────┘

Rules: - Each layer depends only on layers below it - Domain layer has no external dependencies - Changes flow downward; domain is stable

Pros: Familiar, clear separation, easy to understand Cons: Database changes ripple up to domain; infrastructure leaks into business logic; hard to test domain in isolation

Best for: Simple CRUD apps, small teams, low domain complexity


Hexagonal Architecture (Ports & Adapters)

                    ┌──────────────────┐
                    │  Primary Adapters │  ← REST, CLI, UI, gRPC
                    │   (Driving)       │
                    └────────┬─────────┘
                             │ implements
                    ┌────────▼─────────┐
                    │   Inbound Ports   │  ← Use case interfaces
                    ├──────────────────┤
                    │   Domain Core     │  ← Entities, aggregates, domain services
                    │   (No deps out)   │
                    ├──────────────────┤
                    │  Outbound Ports   │  ← Repository, notification, external service interfaces
                    └────────┬─────────┘
                             │ implemented by
                    ┌────────▼─────────┐
                    │ Secondary Adapters │  ← DB, message queue, HTTP clients
                    │   (Driven)        │
                    └──────────────────┘

Core rule: All dependencies point inward toward the domain core. The domain defines interfaces (ports); infrastructure implements them (adapters).

Key concepts: - Primary adapters (driving): Entry points — HTTP controllers, CLI, message consumers - Secondary adapters (driven): Exit points — database repos, external API clients, email senders - Ports: Interfaces owned by the domain, defining what it needs, not how

Pros: - Domain fully testable without infrastructure - Infrastructure swappable without touching domain - Aligns perfectly with Domain-Driven Design - Enables TDD — test domain via ports

Cons: More boilerplate; indirection can obscure flow; overkill for simple apps

Best for: Complex domains, long-lived systems, teams doing DDD, systems needing testability


Clean Architecture (Robert Martin)

┌─────────────────────────────────────┐
│          Frameworks & Drivers       │  ← Web, DB, UI, Devices
├─────────────────────────────────────┤
│        Interface Adapters           │  ← Presenters, Controllers, Gateways
├─────────────────────────────────────┤
│         Application Business        │  ← Use Cases / Interactors
│            Rules                    │
├─────────────────────────────────────┤
│       Enterprise Business Rules     │  ← Entities
└─────────────────────────────────────┘

Dependency Rule: Inner circles know nothing of outer circles. Dependencies point inward.

Differences from Hexagonal: - More prescriptive layer names - Use cases (interactors) are explicit layer between domain and adapters - Entities are innermost (enterprise-wide), use cases are application-specific

Pros: Very explicit boundaries; scream architecture (package by feature); good for large teams Cons: More layers = more mapping/boilerplate; can feel rigid

Best for: Large enterprise apps, regulated environments, teams needing strict boundaries


Onion Architecture (Jeffrey Palermo)

Similar to Clean but emphasizes: - Domain model at center (entities + domain services) - Application services orchestrate domain - Infrastructure at outer edge - Dependency inversion via interfaces in domain layer

Key insight: "The more you depend on things that change, the more you'll have to change."


Domain-Driven Design (DDD) — Strategic Architecture

DDD is not an architecture style per se, but it produces architectural patterns:

Bounded Contexts

Explicit boundaries within which a domain model is consistent and valid. - Different parts of a system can model "Customer" differently - Contexts communicate via Context Maps (conformist, anticorruption layer, shared kernel, etc.)

Context Map Patterns

Pattern Description
Shared Kernel Subset of model shared explicitly; requires coordination
Customer/Supplier Upstream/downstream relationship; downstream defines needs
Conformist Downstream conforms to upstream model (no negotiation)
Anticorruption Layer Translation layer protects downstream from upstream model
Open Host Service Published protocol for multiple consumers
Published Language Well-documented shared language (e.g., OpenAPI, Protobuf)

Microservices Architecture

Not a style — an organizational/deployment pattern that uses architectural styles internally.

Each service typically uses: - Hexagonal/Clean internally - Own database (database per service) - Communicates via async messaging or synchronous API

Challenges: Distributed system complexity, data consistency, operational overhead, network latency

Best for: Large organizations, independent deployability needs, polyglot teams


Modular Monolith

A single deployable unit with internal modular boundaries (bounded contexts as modules).

┌────────────────────────────────────┐
│            Application             │
├─────────┬─────────┬────────────────┤
│ Module A│ Module B│ Module C       │  ← Bounded contexts as packages
│ (DDD)   │ (DDD)   │ (DDD)          │
├─────────┴─────────┴────────────────┤
│         Shared Kernel              │  ← Minimal shared code
└────────────────────────────────────┘

Pros: Simpler operations, easier refactoring, transactions across modules, no network latency Cons: Requires discipline to maintain boundaries; single failure domain

Best for: Most applications! Start here. Extract services only when organizational/scale demands it.


Comparison Matrix

Concern Layered Hexagonal Clean Modular Monolith Microservices
Testability (domain) Low High High High High
Infrastructure swap Hard Easy Easy Medium Easy
Boilerplate Low Medium High Low High
Learning curve Low Medium Medium Low High
Deploy simplicity High High High High Low
Team scaling Low Medium Medium Medium High
DDD alignment Low High High High High

Decision Guidance

Situation Recommended
Simple CRUD, small team, short timeline Layered or Modular Monolith
Complex domain, long-lived, needs testability Hexagonal / Clean
Multiple teams, independent deployment needs Modular Monolith → Microservices
Regulated, audit-heavy, strict boundaries Clean Architecture
Greenfield, uncertain domain Modular Monolith (extract later)

Key Principles Across All Styles

  1. Dependency inversion — Domain defines interfaces; infrastructure implements
  2. Separation of concerns — Business logic separate from infrastructure
  3. Boundaries align with change — Modules/bounded contexts = deployment/change units
  4. Explicit contracts — Ports, interfaces, APIs define integration points
  5. Testability as a design driver — If it's hard to test, the architecture is wrong

  • Design Principles — Principles that inform these styles
  • Domain Driven Design — DDD tactical/strategic patterns
  • CQRS Event Sourcing — Advanced patterns for complex domains
  • Architecture Decision Records — Document your architectural choices
  • Integration Patterns — How services/modules communicate
  • Team Topologies — Team structure should match architecture

Sources: AWS Prescriptive Guidance (Hexagonal), Clean Architecture (Robert Martin), Onion Architecture (Palermo), Domain-Driven Design (Evans), Software Engineering Guide, Martin Fowler, Microsoft Clean Architecture

From the Software Engineering research vault · CC BY 4.0 · all notes · raw markdown