Reshaping a Microservice with DDD: A Practical Layered Architecture Guide

This article shares the author's experience of redesigning a microservice using Domain‑Driven Design, detailing a standardized layered architecture, directory structure, model conversions, common pitfalls, and practical recommendations to improve code clarity and testing efficiency.

Eric Tech Circle
Eric Tech Circle
Eric Tech Circle
Reshaping a Microservice with DDD: A Practical Layered Architecture Guide

Background

In 2021 a microservice project attempted to adopt Domain‑Driven Design (DDD) but suffered from inconsistent team understanding, incomplete conventions, and a focus on rapid implementation. To provide a concrete reference, one microservice was rebuilt with a strict DDD layered architecture and used as a teaching example.

Layered Architecture

The architecture follows the Dependency Inversion Principle: higher‑level modules depend on abstractions, while lower‑level infrastructure implements those abstractions. Five core layers are defined.

Presentation (User Interface) Layer : adapts services for front‑end consumption, handles RESTful requests, parses configuration, and assembles data for the application layer.

Application Layer : composes and orchestrates services to satisfy rapidly changing business processes; contains application services, command/query handlers, and event‑related code.

Domain Layer : implements core business logic; includes aggregates, entities, value objects, domain services, and domain events that together form business capabilities.

Infrastructure Layer : provides technical foundations for all other layers; hosts third‑party integrations, database persistence, and generic utilities.

Common Layer : stores shared code such as exception definitions, enums, and other cross‑cutting concerns.

Code Structure

Top‑level Packages

presentation
application
domain
infrastructure
common

Each top‑level package contains sub‑packages with specific responsibilities.

presentation : assembler, vo, facade – converts domain data to view objects and exposes coarse‑grained controller interfaces.

application : assembler, dto, event ( publish / subscribe), service, task – handles command/query processing, object conversion, event publishing, and scheduled tasks.

domain : organized by aggregates; each aggregate contains shared, model, entity, event, repository, service, util sub‑folders.

infrastructure : config, persistence, entity, converter, repository, integration – implements persistence adapters, configuration, and external system clients.

common : shared utilities, exception hierarchy, enums, etc.

Domain Model Organization

Each aggregate follows a clear boundary:

shared : abstract interfaces and base classes used across aggregates.

model : aggregate root, entities, value objects, and factories (rich domain model).

event : domain events emitted by the aggregate.

repository : interface defining persistence operations for the aggregate; a single repository per aggregate.

service : domain services that encapsulate business logic spanning multiple entities.

util : helper classes specific to the aggregate.

Data Object Types

PO (Persistent Object) : one‑to‑one mapping with database tables; used only in the infrastructure persistence layer.

DO (Domain Object) : runtime entities that carry core business state inside the domain layer.

DTO (Data Transfer Object) : carries data between the presentation layer, application layer, or across microservices.

VO (View Object) : packages data for UI components or API responses; typically built from DTOs by the presentation layer.

Model conversion diagram
Model conversion diagram

Observed Challenges

Excessive object conversion : PO → DO → DTO → VO introduces boilerplate and performance overhead.

Domain logic leakage : Business logic is often placed in application services instead of the domain layer, blurring responsibilities.

ORM complexity : Mixing multiple ORM frameworks (e.g., JPA, MyBatis) without clear guidelines leads to confusing data‑access code.

Unclear external client placement : Integration code is scattered between application and infrastructure layers.

Query performance & coding efficiency : Simple read‑only queries may require traversing four layers, raising doubts about DDD practicality.

Practical Recommendation for Simple Read‑Only Operations

Flow: Controller → Application Service (Query Executor) → Infrastructure (MyBatis Mapper) Input: Command object → MyBatis ExampleDO Output: DataObject → DTO (merged DetailVO and Application DTO; no separate VO)

Architectural Enforcement

The project introduces an archunit dependency to enforce layer boundaries, ensuring that higher layers never depend on lower implementation details.

ArchUnit layer constraints
ArchUnit layer constraints

Reference Diagram

Overall layered architecture
Overall layered architecture
Javamicroservicesbackend developmentDomain-Driven DesignDDDLayered Architecture
Eric Tech Circle
Written by

Eric Tech Circle

Backend team lead & architect with 10+ years experience, full‑stack engineer, sharing insights and solo development practice.

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.