From Hexagonal Architecture to DDD: A Practical Evolution Path for Your System

This article explains how hexagonal architecture serves as a solid foundation for gradually introducing domain‑driven design, outlines the stages of evolution, highlights common pitfalls, and provides concrete code examples to help teams decide when to move beyond hexagonal alone.

Code Wrench
Code Wrench
Code Wrench
From Hexagonal Architecture to DDD: A Practical Evolution Path for Your System

Why Not Jump Directly to DDD

Most systems lack the soil for full DDD at the start.

Reasons include unstable business shape, rapidly changing domain knowledge, insufficient team consensus, and architecture complexity that slows delivery.

Business form is still unstable

Domain knowledge changes quickly

Team lacks shared understanding of the domain

Complex architecture hinders delivery speed

Forcing DDD at this stage often results in a domain layer that is merely DTOs, bloated services, and DDD becoming just a folder‑structure exercise. The real issue is timing, not the DDD concept itself.

The Real Value of Hexagonal Architecture: Laying the Groundwork

Hexagonal architecture enforces three constraints:

Business core does not depend on technical implementations.

External capabilities are accessed through interfaces.

Dependencies point only inward.

These constraints yield immediate benefits:

Business code stays stable.

Technical changes are kept outside the boundary.

Core logic gains long‑term evolvability.

Hexagonal solves “don’t keep writing bad code”.

This stability is a prerequisite for moving toward DDD.

Stage 1: Hexagonal Only (Normal Phase)

Typical characteristics:

Clear application core.

Port/Adapter concepts are present.

Domain layer is thin or absent.

Code shape usually looks like:

Application layer orchestrates workflows.

Business rules appear as simple if checks or validations.

Complexity stems mainly from workflow and external system integration.

Introducing aggregates, entities, or value objects at this point adds unnecessary mental load.

Stage 2: When Hexagonal Is Not Enough

Signals that DDD may be needed are business‑level changes, not technical problems.

The same business rule appears in multiple places.

A concept has different meanings across the codebase.

State transitions become increasingly complex.

Changing a rule risks missing scenarios.

The problem shifts from code structure to loss of business understanding.

This is the moment DDD should be considered.

Stage 3: Introducing a Domain Model Inside Hexagonal

DDD does not replace hexagonal; it lives inside it. The first concrete step is to extract rules from procedural code.

Extract “rules” from flow

if amount <= 0 {
    return errors.New("invalid amount")
}

Refactor into a domain object:

type Order struct {
    Amount int64
}

func (o *Order) Validate() error {
    if o.Amount <= 0 {
        return errors.New("invalid amount")
    }
    return nil
}

Rules now have a dedicated home.

Business starts to take shape.

This marks the first substantive move from hexagonal toward DDD.

Stage 4: Letting Domain Objects Carry Invariance

As rules accumulate, validation expands beyond simple checks, state transitions acquire constraints, and operations become tightly coupled.

Domain objects begin to handle:

State legality.

Behavioral constraints.

Business invariants.

Example:

func (o *Order) Pay() error {
    if o.Status != Created {
        return errors.New("invalid state")
    }
    o.Status = Paid
    return nil
}

Reaching this point means you have entered a lightweight DDD stage.

Stage 5: Identify Aggregates, Not Design Them

Aggregates are forced out by the problem, not designed in.

When you notice that certain objects must be modified together, transaction boundaries become clear, and concurrency issues surface, aggregate boundaries emerge. Avoid premature splitting merely to achieve “DDD completeness”.

Core Principles of the Incremental Path

Architecture precedes modeling – without stable boundaries, domain models get polluted by technical concerns.

Evolution over design – DDD is a continuous adjustment, not a one‑off project.

Attack complexity where it originates – technical complexity → hexagonal; business complexity → DDD.

One‑Line Summary (Engineering View)

Hexagonal is the foundation; DDD grows on top of that foundation.

Before rushing into DDD, ensure your service layer isn’t the bottleneck; drawing clear boundaries is more important than any modeling effort.

True mature architecture evolves naturally with business growth without losing control.

software architectureDomain-Driven DesignClean ArchitectureDDDHexagonal Architectureincremental evolution
Code Wrench
Written by

Code Wrench

Focuses on code debugging, performance optimization, and real-world engineering, sharing efficient development tips and pitfall guides. We break down technical challenges in a down-to-earth style, helping you craft handy tools so every line of code becomes a problem‑solving weapon. 🔧💻

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.