Hexagonal Architecture, also known as Ports and Adapters, is a powerful pattern for building modular, testable, and adaptable microservices. In this blog, weβll walk through:
- What Hexagonal Architecture is
- How to structure your Spring Boot microservice using this pattern
- Folder-by-folder explanation
- Real-world code snippets
- Why it matters
π§ What Is Hexagonal Architecture?
Hexagonal Architecture is about keeping the core business logic independent from external systems like databases, HTTP servers, messaging systems, etc.
Your application should talk to the world through well-defined "ports", and the world connects to your application via "adapters".
πΌοΈ Diagram: High-Level View
+---------------------------+
| Inbound Adapters |
| (REST, Kafka, GraphQL) |
+-------------+-------------+
|
+--------βΌ--------+
| Inbound Port |
+--------+--------+
|
+---------βΌ----------+
| Application / |
| Use Cases |
+---------+----------+
|
+--------βΌ--------+
| Domain Layer |
+--------+--------+
|
+--------βΌ--------+
| Outbound Port |
+--------+--------+
|
+-------------βΌ-------------+
| Outbound Adapters |
| (DB, REST APIs, Files, etc)|
+---------------------------+
π§³ Real-World Folder Structure
src
βββ main
βββ java
βββ com.example.orderms
βββ domain
β βββ model
β βββ repository β Outbound Ports
β βββ service
β
βββ application
β βββ usecase β Inbound Ports
β
βββ adapter
β βββ inbound β REST, Kafka, etc.
β βββ outbound β DB, REST Clients, etc.
β
βββ config β Spring Configuration
β
βββ OrdermsApplication.java
π Detailed Folder Breakdown
1. π¦ domain/
β Core Business Logic (Pure Java)
Contains your business entities, domain services, and interfaces that define what your application depends on. No Spring, no annotations β just clean domain logic.
β Structure:
domain
βββ model
β βββ Order.java
βββ repository
β βββ OrderRepository.java
βββ service
βββ OrderService.java
β
Order.java
public class Order {
private String id;
private String productId;
private int quantity;
private OrderStatus status;
// constructors, getters, setters
}
β
OrderRepository.java
public interface OrderRepository {
Order save(Order order);
Optional<Order> findById(String id);
}
This is a Port β we will create an adapter to implement it using JPA.
2. βοΈ application/usecase/
β Application Logic (Use Cases)
This layer orchestrates domain logic. It's the command center of the app, delegating to domain services or calling outbound ports.
β Example:
application
βββ usecase
βββ PlaceOrderUseCase.java
βββ CancelOrderUseCase.java
β
PlaceOrderUseCase.java
public interface PlaceOrderUseCase {
Order execute(OrderRequestDTO request);
}
β
PlaceOrderService.java
@Service
public class PlaceOrderService implements PlaceOrderUseCase {
private final OrderRepository repository;
public PlaceOrderService(OrderRepository repository) {
this.repository = repository;
}
public Order execute(OrderRequestDTO request) {
Order order = new Order(UUID.randomUUID().toString(), request.getProductId(), request.getQuantity(), OrderStatus.NEW);
return repository.save(order);
}
}
This implements the Inbound Port
PlaceOrderUseCase
.
3. π adapter/inbound/
β Adapters That Call the App (REST, Kafka)
Contains external-facing logic that invokes use cases, such as HTTP controllers, Kafka consumers, CLI commands, etc.
β Structure:
adapter
βββ inbound
βββ rest
βββ OrderController.java
βββ dto
βββ OrderRequestDTO.java
βββ OrderResponseDTO.java
β
OrderController.java
@RestController
@RequestMapping("/orders")
public class OrderController {
private final PlaceOrderUseCase placeOrder;
public OrderController(PlaceOrderUseCase placeOrder) {
this.placeOrder = placeOrder;
}
@PostMapping
public ResponseEntity<OrderResponseDTO> place(@RequestBody OrderRequestDTO request) {
return ResponseEntity.ok(OrderMapper.toResponseDTO(placeOrder.execute(request)));
}
}
4. ποΈ adapter/outbound/
β Adapters the App Calls (Database, APIs)
Implements Outbound Ports using Spring Data JPA, Feign, WebClient, etc.
β Structure:
adapter
βββ outbound
βββ persistence
βββ OrderEntity.java
βββ JpaOrderRepository.java
βββ OrderMapper.java
βββ OrderPersistenceAdapter.java
β
JpaOrderRepository.java
public interface JpaOrderRepository extends JpaRepository<OrderEntity, String> {
}
β
OrderPersistenceAdapter.java
@Component
public class OrderPersistenceAdapter implements OrderRepository {
private final JpaOrderRepository jpaRepo;
public OrderPersistenceAdapter(JpaOrderRepository jpaRepo) {
this.jpaRepo = jpaRepo;
}
@Override
public Order save(Order order) {
return OrderMapper.toDomain(jpaRepo.save(OrderMapper.toEntity(order)));
}
@Override
public Optional<Order> findById(String id) {
return jpaRepo.findById(id).map(OrderMapper::toDomain);
}
}
This adapter implements the outbound port from the domain.
5. π οΈ config/
β Spring Boot Configs
Wires beans and defines application-wide settings.
β Structure:
config
βββ WebConfig.java
βββ AdapterConfig.java
βββ SwaggerConfig.java
β
AdapterConfig.java
@Configuration
public class AdapterConfig {
@Bean
public PlaceOrderUseCase placeOrderUseCase(OrderRepository repository) {
return new PlaceOrderService(repository);
}
}
π Data Flow in Action
User β REST API β UseCase β Domain Logic β Outbound Port β Persistence Adapter β DB
π§ͺ Testing Strategy
Layer | Test Type | Tools |
---|---|---|
Domain | Unit Tests | JUnit, Mockito |
UseCase | Unit/Mock Tests | JUnit |
Adapters | Integration | TestContainers |
Controller | Web Test | Spring MockMvc |
β Benefits of This Architecture
Feature | Benefit |
---|---|
Separation of concerns | Clear boundaries between logic and tech |
Framework independence | Domain is pure Java |
Adapter pluggability | Swap DB, APIs, UIs easily |
Easier testing | Domain logic tested without Spring |
Scalability & maintainability | Perfect for microservices |
π¦ Bootstrap Project Template
I can generate this folder structure into a ready-to-run Maven or Gradle Spring Boot project, complete with:
- Swagger
- JPA
- Flyway
- Testcontainers
- Profiles
Let me know if you want that.
π§ Summary
Hexagonal architecture helps you design robust, clean, and future-proof microservices by enforcing clear boundaries. With Spring Boot, it's easy to adopt this pattern by structuring your app with:
- Domain (core business logic)
- Application (use cases)
- Ports (interfaces)
- Adapters (framework integrations)
If youβre building microservices in a modern, test-driven, and maintainable wayβthis structure will take you far.
Top comments (0)