Implementing DDD with Hexagonal Architecture in Spring Boot

  • 4.7/5
  • 235
  • Sep 10, 2024

Domain-Driven Design (DDD) is a software development approach introduced by Eric Evans in his book Domain-Driven Design: Tackling Complexity in the Heart of Software".

DDD is particularly beneficial when your application has complex business rules and requirements that need to be clearly represented in the code. DDD ensures that the software design is closely aligned with the business domain, making it easier to adapt to changes in business requirements.

When you have a large system with multiple teams, DDD helps manage complexity by defining bounded contexts and ensuring consistent communication through ubiquitous language. By defining bounded contexts, DDD helps in managing complexity in large systems, ensuring that different parts of the system are not tightly coupled.

DDD encourages focusing on the most important part of the business (the core domain) and dedicating the most effort to modeling that area well.

Core Concepts

1) Domain: The domain is the area of knowledge or activity that the software is designed to address. For example, in an e-commerce application, the domain could include concepts like Orders, Customers, and Products.

It contains the core business logic and rules. Its primary role is to model the business domain independently of other concerns like persistence or application logic.

2) Application: The application layer orchestrates the use cases by coordinating domain objects and services to solve business problems. It should not contain business logic itself but should delegate that to the domain layer.

The application folder contains the application's use cases or business processes. This layer coordinates the flow of data between the domain layer and the external world. It's responsible for application-specific logic but doesn't contain business rules.

3) Infrastructure: The infrastructure folder contains implementations of interfaces defined in the domain and application layers. It handles technical concerns like data storage, messaging, or third-party integrations.

4) Ubiquitous Language: In DDD, the development team (developers, domain experts, stakeholders) shares a common language—the Ubiquitous Language—that is used consistently throughout the project. This language should reflect the business domain and be understood by both technical and non-technical stakeholders/. For example, terms like "Order", "Customer", and "Order Status" should be used in code, documentation, and conversations to ensure a shared understanding.

5) Bounded Context: A bounded context defines the boundaries within which a particular model is valid. Different parts of a large system may use the same terms but have different meanings in different contexts. For example, "Order" in the context of an order management system might mean something different than "Order" in the context of a payment system. Each bounded context has its own model, and the interactions between contexts are clearly defined.

Hexagonal Architecture and Domain-Driven Design (DDD)

Hexagonal Architecture and Domain-Driven Design (DDD) complement each other by providing a structured approach to building complex, maintainable, and scalable systems. Here’s how Hexagonal Architecture fits within the principles of DDD:

1) In DDD, the Domain Layer is the most important part of the application. In Hexagonal Architecture this core domain logic is placed at the center of the architecture, completely independent of external concerns.

2) In DDD, the Application Layer is responsible for coordinating tasks and delegating business logic to the domain layer. In Hexagonal Architecture the application layer is often implemented as an inbound port.

3) In DDD, the Infrastructure Layer deals with technical concerns like database interaction, external service calls, messaging, etc. Hexagonal Architecture clearly separates these concerns by having adapters that implement the ports (interfaces) defined by the core domain or application layer.

4) Both Hexagonal Architecture and DDD emphasize the Dependency Inversion Principle. The core domain logic should not depend on the infrastructure layer or external services. Instead, the infrastructure layer should depend on the domain, which is facilitated by interfaces (ports).

What is Hexagonal Architecture ?

Hexagonal Architecture, also known as Ports and Adapters Architecture, is a software design pattern that promotes the separation of concerns by isolating the core business logic from external systems like databases, UI, messaging systems, and other external APIs.

The goal is to make the application more maintainable, testable, and adaptable to changes by structuring it in a way that the business logic (core domain) is independent of external interfaces.

Core Concepts

1) Core Domain (Center of the Hexagon): The heart of the application that contains all the business rules and logic. This domain layer should be independent of any external frameworks, databases, UI, or external services. It's purely focused on the problem domain.

2) Ports (Interfaces): These are interfaces that define the interactions between the core domain and the outside world. Ports allow the domain to be decoupled from specific implementations. Ports can be classified as:

2.1) Inbound Ports: Interfaces that define the ways external systems (e.g., UI, REST controllers) can interact with the domain. These can be use case-driven interfaces.

2.2) Outbound Ports: Interfaces that define how the domain interacts with external systems (e.g., databases, messaging services, other APIs).

3) Adapters: These are the actual implementations of the Ports. Adapters adapt the external systems to the core domain by implementing the Port interfaces.

3.1) Inbound Adapters: REST controllers, CLI commands, or other entry points that implement inbound ports.

3.2) Outbound Adapters: Repositories, REST clients, messaging implementations, etc., that implement outbound ports.

4) Dependency Flow: The domain layer does not depend on any adapter or infrastructure. Instead, dependencies flow inwards from the adapters to the domain. This ensures that the business logic is at the core of the architecture and remains insulated from changes in the external world.

Benefits of Hexagonal Architecture

1) Since the core domain logic is decoupled from external systems, it can be tested independently using mock ports/adapters.

2) Changes in the infrastructure (e.g., switching databases, modifying external services) do not affect the core business logic.

3) You can easily swap adapters without impacting the core domain. For example, you can change the database technology or UI without modifying the business logic.

4) Different concerns (e.g., business logic, persistence, external APIs) are neatly separated into different layers or modules.

Implementing Domain-Driven Design (DDD) with Spring Boot

Let's implement a simple e-commerce application service, "order-service," in a microservice architecture using Domain-Driven Design (DDD) and Hexagonal Architecture in Spring Boot with Postgres DB and a downstream call.

1) Initialize Spring Boot Project

Use Spring Initializr or create a Maven/Gradle project. Add dependencies - Spring Boot Web, Spring Data JPA, PostgreSQL Driver, Lombok (optional), Spring Boot DevTools (optional) and Swagger/OpenAPI for API documentation (optional).

2) Project Structure

Here's how the package structure would look:

1. OrderServiceApplication.java

This is the entry point of the Spring Boot application.

2. application.ports.input

3. application.ports.output

4. domain.model

5. domain.exception

6. domain.service

7. infrastructure.adapters.config

8. infrastructure.adapters.input.rest

9. infrastructure.adapters.output.persistence.entity

10. infrastructure.adapters.output.persistence.repository

11. infrastructure.adapters.output.persistence.mapper

12. infrastructure.adapters.output.client

13. infrastructure.adapters.output.persistence

14. src/main/resources/application.yml

Source Code: GitHub

Index
Implementing DDD with Hexagonal Architecture in Spring Boot

26 min

Imperative, Async Blocking, Reactive & Virtual Threads

4 min