Understanding Hexagonal Architecture with Java Spring Boot Examples

Search for a command to run...

No comments yet. Be the first to comment.
1. Sạch Phải công nhận là Sing sạch, đi đâu cũng thấy có người đang quét dọn, tỉa cành, gom rác, cắt cỏ,… Chi phí để duy trì môi trường cảnh quan chắc cũng không hề nhỏ. 2. Giao thông công cộng Bên này chủ yếu đi bằng tàu điện (MRT) và xe bus, chi ph...
In the previous article, I covered the basic concepts and introduced a 5-step process for applying DDD in practice. Today, I will bring you a bigger challenge. In this article, we will work through an

I. Why DDD matters? A Bigger Picture Over the years, as business needs have grown increasingly complex, our application systems have evolved - from monoliths to SOA, and now to microservices. This evolution demands a rational approach to component de...

Behind every robust software system lies a suite of well-structured unit tests. But what defines a great unit test? In this article, we’ll examine its anatomy and best practices to ensure your tests are both reliable and effective. I. A Bigger Pictur...

This is a nice feedback from my Singaporean Scrum Master for 2024. According to Vietnamese beliefs, 2024 marked the final year of a challenging three-year period (Tam Tai) for those born in 1996, a time filled with uncertainties and difficulties. Al...
If your software has been running for over 10 years on an outdated framework or database, and you're looking to upgrade to a modern solution but still want to keep the core business code, this is when the significance of a well-designed architecture becomes clear.
An architectural pattern which creating loosely coupled application components that can be easily connected to their software environment by means of ports and adapters. This makes components exchangeable at any level.

Hexagonal or Clean architecture have the same objective, which is the separation of concerns.
Divide the software into layers
Independent of frameworks, UI, database, external services
Testable without UI, database, web server, or any other external services
You can see the similarity between them.

Port is an interface layer that define how core domain layer interact with external components (frameworks, external services, databases,…).
Inbound ports: handle input to the core, such as user commands or requests from clients or other systems.
Outbound ports: define how the core can communicate with external systems like databases or APIs.
Adapters are the implementations of ports.
Inbound Adapters: These include things like REST controllers, CLI interfaces, or event-driven consumers that handle user input or external events.
Outbound Adapters: These are responsible for communicating with external systems, such as databases, third-party services, or message brokers.
This is the core domain layer where holds domain entities and business logic. It’s isolated from frameworks and external components and is the most stable part of the software.
An application service acts as a facade through which clients interact with the domain model. This is where ports are defined.
It control database transactions, orchestrates business operations but should not make any business decisions (should be in domain layer).
Java 17
Spring Boot 3
Intelij or any Java IDE
We start with https://start.spring.io/ to create a new Spring Boot framework. I chose Springweb and Lombok dependencies only for the demo.

You can check the project structure on the website before downloading it to your local.

I will implement a booking service where we can create a booking as a sample.
To implement 3 layers of the architecture, I created 3 Java modules:
Domain: store domain entities and business logic
Application: store inbound and outbound ports
Tech Framework: includes frameworks and adapters for database, controllers, external services,…

In which, Application module depends on Domain module:

and Tech Framework module depends on both Application and Domain:

As you can see, the domain has no dependency on frameworks or external components.
Firstly, I have a BookingDomainEntity (just a sample, ignore the attributes pls :D)
import java.time.LocalDateTime;
import lombok.*;
@Getter
@Setter
@Builder
@AllArgsConstructor
@ToString
public class BookingDomainEntity {
private long id;
private long userId;
private long totalAmount;
private String pickUpAddress;
private String dropOffAddress;
private LocalDateTime createdOn;
}
Secondly, I have commands for business logic. To keep it simple, I don’t use command for booking creation.

As I showed you, we have inbound and outbound ports in this layer.
To creat a booking, I have a BookingCommandInboundPort so that it can be called by controllers from Tech Framework layer.
@Slf4j
public class BookingCommandInboundPort {
private final BookingRepositoryOutboundPort bookingRepositoryOutboundPort;
public BookingCommandInboundPort(BookingRepositoryOutboundPort bookingRepositoryOutboundPort) {
this.bookingRepositoryOutboundPort = bookingRepositoryOutboundPort;
}
public void save(BookingDomainEntity entity) {
log.info("Saving BookingDomainEntity");
bookingRepositoryOutboundPort.save(entity);
}
}
And a BookingRepositoryOutboundPort for saving a new booking to database:
public interface BookingRepositoryOutboundPort {
List<BookingDomainEntity> findAll();
void save(BookingDomainEntity entity);
}
Then the application service should look like this:

Finally, we implement the outsidemost layer. The dependencies that I chose previously is for this layer. I also added mapstruct library for object mapping.

We also have inbound and outbound structure similar to the application layer.

Firstly, I have BookingEntity and BookingRepository for persistence. To keep it simple, I don’t use database and only create a dummy entity and repository:
// should have @Entity in real project
@Data
@Builder
public class BookingEntity {
private long id;
private long userId;
private long totalAmount;
private String pickUpAddress;
private String dropOffAddress;
private LocalDateTime createdOn;
}
@Repository // Dummy repository => should be an interface and extend JpaRepository
public class BookingRepository {
List<BookingEntity> findAll() {
return List.of(BookingEntity.builder().build());
}
BookingEntity save(BookingEntity entity) {
// dummy save method
return entity;
}
}
Secondly, I have a BookingRepositoryAdapter that implement BookingRepositoryOutboundPort (from application layer):
@Component
@RequiredArgsConstructor
public class BookingRepositoryAdapter implements BookingRepositoryOutboundPort {
private final BookingRepository bookingRepository;
private final BookingMapper mapper;
@Override
public List<BookingDomainEntity> findAll() {
List<BookingEntity> entities = bookingRepository.findAll();
return mapper.toBookingDomainEntity(entities);
}
@Override
public void save(BookingDomainEntity entity) {
// Transform Aggregate Root 's states into JPA Entity 's states before saving them into database
bookingRepository.save(mapper.toBookingJPAEntity(entity));
}
}
and a BookingMapper for object mapping. I strongly recommend you try Mapstruct to eliminate these mapping boilerplate code.
@Mapper(componentModel = "spring")
public interface BookingMapper {
@Mapping(target = "createdOn", ignore = true)
BookingDomainEntity toBookingDomainEntity(BookingRequest request);
BookingEntity toBookingJPAEntity(BookingDomainEntity entity);
List<BookingDomainEntity> toBookingDomainEntity(List<BookingEntity> entities);
}
Ok finally, we need a BookingController to handle client requests:
@RestController
@RequestMapping("/api/v1/bookings")
@RequiredArgsConstructor
public class BookingController {
// Inbound port in application layer
private final BookingCommandInboundPort bookingCommandInboundPort;
private final BookingMapper mapper;
@PostMapping(
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<BookingResponse> createBooking(@RequestBody BookingRequest request) {
bookingCommandInboundPort.save(mapper.toBookingDomainEntity(request));
return ResponseEntity.ok().build();
}
}
If you are feeling lost in this architecture, you are not alone :D. The flow is quite complex and can be confusing. Don't worry, I’ve sketched out a request flow so you can better visualize the entire process we just discussed:

From this perspective, it's clear that if I want to replace my Spring Boot with Quarkus in the future, I can retain the application and domain modules since they are not framework-dependent. This will save a lot of effort, trust me! 😊
You can check the full code here.
We've just covered the definition and a hands-on example of Hexagonal Architecture. You might not fully understand it after reading, but I recommend trying out the sample first. Then, when you come back, I believe things will be clearer for you.
Next time, I will implement a DDD sample in this repo and we will discuss about why we should apply Hexagonal along with DDD for our project.
See yaa!!!