Skip to content
Home
Software Architecture Patterns: Layered, Hexagonal, and More

Software Architecture Patterns: Layered, Hexagonal, and More

Software Design Software Design 7 min read 1485 words Beginner ExcellentWiki Editorial Team

Software architecture is the high-level structure of a software system. It defines the components, their relationships, and the principles governing their design and evolution. Architecture patterns are reusable solutions to common structural problems.

Choosing the right architecture pattern is one of the most important decisions a development team makes. The pattern determines how the system can grow, how teams can work, and how well the system can adapt to changing requirements.

Layered Architecture

Layered architecture is the most widely used pattern. It organizes code into horizontal layers, each with a specific responsibility. The most common layers are presentation, business logic, and data access.

How It Works

Layers are stacked vertically. Each layer depends only on the layer directly beneath it. The presentation layer handles user interaction and delegates to the business logic layer. The business logic layer contains the core rules and coordinates with the data access layer. The data access layer handles persistence and may communicate with external systems.

@RestController
public class OrderController {
    private final OrderService service;

    @PostMapping("/orders")
    public OrderResponse createOrder(@RequestBody OrderRequest request) {
        return service.createOrder(request);
    }
---

@Service
public class OrderService {
    private final OrderRepository repository;

    public OrderResponse createOrder(OrderRequest request) {
        Order order = Order.from(request);
        return OrderResponse.from(repository.save(order));
    }
---

@Repository
public class OrderRepository {
    public Order save(Order order) {
        return order;
    }
---

Strengths and Weaknesses

Layered architecture is simple, familiar, and easy to understand. Most developers know how it works. It enforces separation of concerns — each layer has a clear job.

The main weakness is that it can lead to monolithic designs. Layers tend to grow thick over time, and the pattern does not prevent cross-cutting concerns from leaking across layers. A common anti-pattern is the anaemic domain model where business logic leaks into the service layer, leaving the domain objects as simple data holders.

Hexagonal Architecture

Alistair Cockburn introduced hexagonal architecture — also called ports and adapters — to address the coupling problems of layered architecture. The core idea is that the application is at the center, surrounded by ports that define interfaces, and adapters that implement those interfaces for specific technologies.

How It Works

The domain model sits at the center with no external dependencies. Around it are ports — interfaces that define how the outside world can interact with the application. Outside the ports are adapters — implementations that connect to databases, web frameworks, message queues, and other external systems.

public interface OrderRepository {
    Order save(Order order);
    Optional<Order> findById(String id);
---

@Repository
public class JpaOrderRepository implements OrderRepository {
    private final SpringDataJpaRepository jpa;

    public Order save(Order order) {
        OrderEntity entity = OrderEntity.from(order);
        return jpa.save(entity).toDomain();
    }
---

The dependency rule is strict: everything depends inward. The domain knows nothing about Spring, JPA, or HTTP. Adapters translate between the domain interface and the specific technology.

Benefits

Hexagonal architecture makes the domain testable in isolation. You can test business rules without a database or web server by providing mock adapters. It also makes technology choices replaceable. If you want to swap MySQL for MongoDB, you write a new adapter without touching the domain.

Onion Architecture

Jeffrey Palermo’s onion architecture is similar to hexagonal architecture but with more explicit layers. The innermost layer is the domain model, followed by domain services, application services, and infrastructure.

Each layer can depend only on layers closer to the center. The outer layers implement interfaces defined by inner layers. The pattern emphasizes that the domain is the most important part of the application and should not depend on infrastructure concerns.

Clean Architecture

Robert C. Martin’s clean architecture synthesizes ideas from hexagonal and onion architectures into a set of principles. The architecture produces systems that are independent of frameworks, testable, independent of UI, independent of databases, and independent of external agencies.

The Dependency Rule

The overriding rule is the dependency rule: source code dependencies can only point inward. Nothing in an inner circle can know about something in an outer circle. Outer circles implement interfaces defined by inner circles using dependency injection.

Layers

Clean architecture typically has four layers. Entities encapsulate enterprise-wide business rules. Use cases contain application-specific business rules. Interface adapters convert data between the use cases and external formats. Frameworks and drivers include the web framework, database, and UI.

Event-Driven Architecture

Event-driven architecture decouples components through asynchronous event publication and consumption. Producers emit events to an event bus (Kafka, RabbitMQ, AWS EventBridge) without knowing which consumers will process them. This enables loose coupling, scalability, and extensibility. Common patterns include event notification (triggering downstream processes), event-carried state transfer (including relevant data in events), and event sourcing (storing state changes as an event log).

Saga Pattern for Distributed Transactions

Microservices distributed transactions use the saga pattern — a sequence of local transactions with compensating actions for rollback. Choreography sagas use event publication to trigger the next step. Orchestration sagas use a central coordinator. Each approach handles failures differently, and the choice depends on complexity requirements and team structure.

Choosing a Pattern

No single architecture pattern is best for every project. Layered architecture works well for simple applications with limited complexity. Hexagonal, onion, and clean architectures are better for complex domains where testability and maintainability are critical.

Consider your team’s experience, the expected lifespan of the application, and the complexity of the business domain. A wrong architecture choice is costly but fixable — the real mistake is not choosing an architecture at all and letting the system evolve without structure.

FAQ

What is the difference between hexagonal and clean architecture? They share the same core principle — domain at the center, external concerns at the edges. Clean architecture defines more explicit layers (entities, use cases, interface adapters, frameworks). Hexagonal architecture focuses on ports and adapters without prescribing layer count.

Should I use layered or hexagonal architecture for a new project? Start with layered architecture for simple projects. Move to hexagonal when you need to isolate the domain from infrastructure concerns, have complex business logic, or anticipate swapping technologies.

How do I enforce the dependency rule? Use separate modules (Maven/Gradle subprojects, npm workspaces) with compile-time dependency checks. Tools like ArchUnit (Java), the import linter (Python), and dependency-cruiser (Node.js) can enforce architectural rules in CI.

Is architecture over-engineering for a small project? Yes, if you apply complex patterns to a simple application. Use layered architecture by default and graduate to hexagonal or clean architecture when the domain logic justifies the investment.

Architectural Decision Records

An Architectural Decision Record (ADR) documents a significant architecture decision, including the context, alternatives considered, and the rationale for the chosen approach. ADRs provide organizational memory — new team members understand why decisions were made without relying on word-of-mouth.

A typical ADR structure:

# ADR-001: Use Event-Driven Architecture for Order Processing

## Status
Accepted

## Context
The order processing system needs to handle peak loads of
10,000 orders/second during Black Friday while integrating
with inventory, payment, and shipping services.

## Decision
Implement an event-driven architecture with Apache Kafka
as the message backbone. Each service processes events
independently and publishes results.

## Consequences
- + Horizontal scalability for each service independently
- + Decoupled deployments
- - Increased operational complexity (Kafka cluster management)
- - Eventual consistency adds complexity for error handling

## Alternatives Considered
- Monolithic processing: simpler but cannot scale independently
- REST synchronous calls: tight coupling under load

Architecture Evaluation Frameworks

The ATAM (Architecture Trade-off Analysis Method) evaluates architecture against quality attributes — performance, security, availability, modifiability, testability, and deployability. Each architectural decision involves trade-offs: caching improves performance but adds complexity, microservices improve deployability but increase latency. Documenting these trade-offs helps stakeholders make informed decisions and prevents architecture by accident.

Monolith-First Architecture

Start with a modular monolith. Extract services only when needed. A modular monolith with clear module boundaries can be split into microservices later, but starting with microservices adds complexity (distributed transactions, network latency, service discovery) before you know whether you need it. The Modular Monolith pattern uses package/module boundaries that could become service boundaries — this preserves the option to split later without paying the distributed systems tax upfront.

Hexagonal Architecture (Ports and Adapters)

Hexagonal architecture isolates business logic from external concerns. The core contains business logic and defines ports (interfaces). Adapters implement ports for specific technologies — a REST adapter, a database adapter, a message queue adapter. The key rule: the core depends on nothing external. Adapters depend on the core and on external libraries. This architecture enables testing business logic without infrastructure, swapping technologies without changing business code, and developing adapters in parallel.

Event Storming

Event Storming is a collaborative workshop technique for discovering domain events and modeling business processes. A room full of stakeholders (domain experts, developers, product owners) uses sticky notes to map events, commands, policies, and read models on a wall. The process reveals inconsistencies in understanding, surfaces hidden business rules, and produces a shared model that everyone agrees on. Event Storming is especially valuable before designing an event-driven system.


Related: Domain-Driven Design | Microservices vs Monolith

Section: Software Design 1485 words 7 min read Beginner 756 articles in section Report inaccuracy Back to top