📘 Overview
This document summarizes the design principles and patterns discussed for building a microservice that follows clean architecture, using Domain-Driven Design (DDD) and Hexagonal Architecture (Ports and Adapters). It also includes practical guidance for abstraction and modularization based on real-world complexity.
🎯 Purpose of This Guide
To answer key questions:
- How do I model the core business logic of my service?
- Where should logic live — domain, application, or infrastructure?
- When should I abstract a layer or break a service into smaller parts?
- How do I apply DDD in a real-world Spring Boot/Kotlin project?
🧩 Real-World Context: Order Microservice
Business Requirements:
- Create an order by fetching data from 6+ external services
- Assemble an
Orderaggregate based on mode, toggle, and context - Save the order to DB and publish Kafka events
- Rollback if any critical step fails
Resulting Questions:
- How do I prevent my
OrderServicefrom becoming a God class? - Should I inject all 6 clients directly into the service?
- Where should I put logic like toggled UUID generation, conditional order assembly?
- How do I structure my project so each part is testable and composable?
⚙️ Architectural Principles
1. Domain-Driven Design (DDD)
- Model the business domain with clarity and intention
- Focus on entities (
Order,People), value objects (Money,Address), aggregates, and domain services - Identify bounded contexts — e.g., Order, People, Logistics
2. Hexagonal Architecture (Ports & Adapters)
- Keep the domain core isolated from frameworks and infrastructure
- Use Ports as interfaces (e.g.,
OrderRepository,OrderPublisher) - Implement those ports in Adapters (e.g.,
JpaOrderRepository,KafkaPublisher) - Structure the project around clear boundaries
3. Clean Application Layer
- Orchestrate workflows through dedicated use case classes like
CreateOrderUseCase,CancelOrderUseCase - Use factories (e.g.,
OrderFactory) to build domain aggregates - Use builders (e.g.,
OrderContextBuilder) to collect data from services
📦 Recommended Project Structure
order-service/
├── domain/
│ ├── model/ # Entities, VOs, Aggregates
│ ├── service/ # Domain services
│ └── event/ # Domain events
│
├── application/
│ ├── usecase/ # Use cases like CreateOrderUseCase
│ ├── factory/ # Factories like OrderFactory
│ └── port/
│ ├── inbound/ # Controller interfaces (optional)
│ └── outbound/ # Ports like OrderRepository, KafkaPublisher
│
├── adapter/
│ ├── inbound/
│ │ ├── api/ # Controllers
│ │ └── kafka/ # Kafka consumers
│ └── outbound/
│ ├── messaging/ # KafkaPublisherImpl
│ └── persistence/ # JpaOrderRepository
│
├── infra/
│ ├── config/ # KafkaConfig, JpaConfig, etc.
│ └── shared/ # EnvConfig, ClockProvider, UUIDGenerator
🧠 Design Guidelines
Domain Layer
- Pure business logic
- No framework dependencies
- Use entities, value objects, and domain services
Application Layer
- Coordinates use cases
- Builds context and invokes domain logic
- Depends on ports (interfaces), not infrastructure
Ports
- Defined in the application layer
- Represent what the domain/use case needs (e.g. repository, publisher)
Adapters
- Implement ports using real infrastructure
- Contain Spring, Kafka, JPA, etc.
- Never depend on the domain directly — only via ports
Infrastructure Layer
- Shared low-level tech utilities and config
- Used by adapters or injected into use cases
🤖 Real-World Abstraction Triggers
You should create a new layer or class when:
- A method takes 6+ parameters → extract a context object
- Logic uses toggles or env flags → extract to a strategy/factory
- One class talks to 5+ services → extract a builder/service
- Use case starts branching heavily → split into distinct classes
- Domain logic can be reused/tested in isolation → move to domain
✅ Key Takeaways
| Concept | Role | | ---------------------- | ------------------------------------------------------------------------ | | DDD | Models core business behavior (what your system is really doing) | | Hexagonal Architecture | Organizes your system around that model cleanly | | Application Layer | Coordinates workflows using the domain and ports | | Ports | Define what is needed (e.g. save, publish), not how | | Adapters | Implement the ports using real technology (Kafka, JPA, REST) | | OrderContextBuilder | Gathers external data; lives in application layer | | OrderFactory | Builds Order aggregate; lives in application layer if it uses env/config | | Domain Layer | Pure rules — entities, value objects, and core invariants |
Build systems where your business logic is free to evolve, unburdened by frameworks. That's what good architecture enables.