Welcome to the Domain-Driven Design (DDD) with Hexagonal Architecture Workshop! This hands-on workshop will guide you through implementing core DDD concepts while building a demo registration system for conference attendees.
This workshop is for anyone who likes to get their hands on a keyboard as part of the learning process. Your project authors believe that architecture is best learned through practice, and this workshop provides a structured way to apply Domain-Driven Design (DDD) principles in a practical context.
There is a great deal of theory in Domain Driven Design. This workshop was built because while the authors love talking about software architecture (their colleagues will verify), they also like getting their hands dirty with code. In fact, your workshop authors believe that it is impossible to understand software architecture without getting your hands on a keyboard and implementing the ideas.
This workshop is for anyone who likes to get their hands on a keyboard as part of the learning process. Your project authors believe that architecture is best learned through practice, and this workshop provides a structured way to apply Domain-Driven Design (DDD) principles in a practical context.
In this introductory workshop, you'll learn to apply Domain-Driven Design principles by building a microservice for managing conference attendee registrations. You'll implement the complete workflow from receiving HTTP requests to persisting data and publishing events, all while maintaining clean architectural boundaries.
By the end of this workshop, you will have implemented an attendee registration system that demonstrates:
- Domain-Driven Design: Business-focused modeling and implementation
- Event-Driven Communication: Asynchronous integration through domain events
- Hexagonal Architecture: Creation of loosely coupled application components that can be easily composed; also known as ports and adapters
- Inbound Adapters: HTTP endpoint implementation
- Outbound Adapters: Persistent storage with proper domain/persistence separation and messaging with Kafka
This workshop implements the Hexagonal Architecture (Ports and Adapters) pattern, ensuring your business logic remains independent of external technologies:
External World β Inbound Adapters β Domain Layer β Outbound Adapters β External Systems
β β β β β
HTTP Requests β REST Endpoints β Business Logic β Event Publisher β Kafka
Aggregates β Repository β Database
The heart of DDD - business entities that encapsulate logic and maintain consistency within their boundaries.
- Events: Record facts that have already occurred (immutable) and most importantly what the business cares about.
- Commands: Represent intentions to change state (can fail)
Orchestrate business workflows that don't naturally belong in a single aggregate.
Model your domain with appropriate object types that reflect business concepts.
Provide a collection-like interface for accessing and persisting aggregates, abstracting database details.
Integration points between the domain and external systems (REST APIs, databases, message queues).
Model your domain with appropriate object types that reflect business concepts.
Each workshop module contains a pre-built directory with stubbed out classes, like the one below.
package dddhexagonalworkshop.conference.attendees.infrastructure;
import dddhexagonalworkshop.conference.attendees.domain.services.AttendeeService;
import dddhexagonalworkshop.conference.attendees.domain.services.RegisterAttendeeCommand;
import io.quarkus.logging.Log;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.net.URI;
/**
* "The application is blissfully ignorant of the nature of the input device. When the application has something to send out, it sends it out through a port to an adapter, * which creates the appropriate signals needed by the receiving technology (human or automated). The application has a semantically sound interaction with the adapters on * all sides of it, without actually knowing the nature of the things on the other side of the adapters."
* Alistair Cockburn, Hexagonal Architecture, 2005.
*
*/
public class AttendeeEndpoint {
}The documentation contains the code to complete the classes:
package dddhexagonalworkshop.conference.attendees.infrastructure;
import dddhexagonalworkshop.conference.attendees.domain.services.AttendeeService;
import dddhexagonalworkshop.conference.attendees.domain.services.RegisterAttendeeCommand;
import io.quarkus.logging.Log;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.net.URI;
/**
* "The application is blissfully ignorant of the nature of the input device. When the application has something to send out, it sends it out through a port to an adapter, * which creates the appropriate signals needed by the receiving technology (human or automated). The application has a semantically sound interaction with the adapters on * all sides of it, without actually knowing the nature of the things on the other side of the adapters."
* Alistair Cockburn, Hexagonal Architecture, 2005.
*
*/
@Path("/attendees")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class AttendeeEndpoint {
@Inject
AttendeeService attendeeService;
@POST
public Response registerAttendee(RegisterAttendeeCommand registerAttendeeCommand) {
Log.debugf("Creating attendee %s", registerAttendeeCommand);
AttendeeDTO attendeeDTO = attendeeService.registerAttendee(registerAttendeeCommand);
Log.debugf("Created attendee %s", attendeeDTO);
return Response.created(URI.create("/" + attendeeDTO.email())).entity(attendeeDTO).build();
}
}You can implement the classes by typing in the supplied code, which is your workshop authors preferred method because we believe it is easier to remember that way, or by copying and pasting. Each step will cover a particular DDD topic.
The examples are not meant to be reflect a production system so you will find, for instance, that validation might not be as complete as it would in a real application.
Each step starts with a tl;dr section containing only code. If you want to get the application up and running as quickly as possible you can copy/paste the code into the stubbed classes without reading the rest of the material.
We think this can be a good approach if you are as impatient as (one of) us, but we hope you go back through the material and read through each step.
A conference attendee registration microservice with:
- REST API for registering attendees
- Business logic that validates registrations
- Event publishing to notify other systems
- Database persistence for attendee data
- Clean architecture that separates concerns
We'll build this system step-by-step, with each piece compiling as we go:
| Module | Component | Focus |
|---|---|---|
| 01 | End to End DDD | Implement a (very) basic workflow |
| 02 | Value Objects | Add more detail to the basic workflow |
| 03 | Anti-Corruption Layer | Implement an Anti Corruption Layer to integrate with external systems |
| 04 | Testability | Focus on testing |
We'll build this system step-by-step, with each piece compiling as we go:
| Step | Component | Focus |
|---|---|---|
| 01 | Events | Capture business facts |
| 02 | Commands | Represent business requests |
| 03 | Result Objects | Combine multiple outputs |
| 04 | Aggregates | Core business logic |
| 05 | Entities | Database mapping |
| 06 | Repositories | Data access layer |
| 07 | Event Publisher | Messaging integration |
| 08 | Domain Services | Workflow orchestration |
| 09 | DTOs | API data contracts |
| 10 | REST Endpoint | HTTP interface |
We'll build this system step-by-step, with each piece compiling as we go:
| Step | Component | Focus |
|---|---|---|
| 01 | Create the Address Value Object | Capture business facts |
| 02 | Update the RegisterAttendeeCommand | Represent business requests |
| 03 | Update the Attendee Aggregate | Package multiple outputs |
| 04 | Update the AttendeeRegisteredEvent | Core business logic |
| 05 | Update the Persistence Layer | Database mapping |
| 06 | Update the AttendeeService | Data access layer |
| 07 | Update the AttendeeDTO | Messaging integration |
We'll build this system step-by-step, with each piece compiling as we go:
| Step | Component | Focus |
|---|---|---|
| 01 | Create the Address Value Object | Capture business facts |
| 02 | Update the RegisterAttendeeCommand | Represent business requests |
| 03 | Update the Attendee Aggregate | Package multiple outputs |
| 04 | Update the AttendeeRegisteredEvent | Core business logic |
| 05 | Update the Persistence Layer | Database mapping |
- Business logic in the right place - not scattered across layers
- Rich domain models that express business concepts clearly
- Clean separation between business rules and technical concerns
- Ports and Adapters pattern that keeps your core domain pure
- Technology independence - swap databases or frameworks easily
- Testable design with clear boundaries
External World β REST β Domain Logic β Events β External Systems
β β β β β
HTTP Requests β Commands β Aggregates β Events β Kafka
β β β
DTOs β Domain Service β Repository β Database
- Follow along step-by-step - don't jump ahead
- Copy code exactly - before experimenting. Once everything is working, experiment all you want
- Ask for help if you get stuck
- Don't optimize or change the code - get it working first
- Don't get stuck on theory questions - ask theory questions!
- Don't get stuck on implementation questions - ask implementation questions!
- Don't panic - the goal is learning, not perfection
- Revisit at a later date - the workshop will be on GitHub, the authors are easy to get in touch with, and happy to help at any time
There are 2 ways to do the workshop:
-
GitHub Codespace: GitHub Codespaces
-
Quarkus' Dev Mode on your laptop: Quarkus Local
- β‘ Supersonic, Subatomic Java: Incredibly fast startup times and low memory usage
- π§ Developer Experience: Live reload during development - see changes instantly
- π³ Container First: Built for Kubernetes and cloud deployment from the ground up
- π¦ Unified Configuration: Single configuration model for all extensions
- π― Standards-Based: Built on proven standards like JAX-RS, CDI, and JPA
Most importantly, Quarkus gets out of your way, allowing you to focus on your code.
Your workshop authors work for Red Hat, the company behind Quarkus, but we believe that Quarkus is the best choice because it allows you to focus on implementing Domain-Driven Design (DDD) concepts without worrying about boilerplate code or complex configurations.
Dev Mode Magic: Quarkus automatically starts and manages external dependencies:
./mvnw quarkus:devThis single command spins up:
- PostgreSQL database for persistence
- Kafka broker for event streaming
- Your application with live reload
- Integrated testing capabilities
Zero Configuration Complexity: Focus on DDD concepts instead of infrastructure setup. Quarkus handles:
- Database schema generation
- Kafka topic creation
- Dependency injection
- REST endpoint configuration
- JSON serialization