Skip to main content

Hexagonal

Hexagonal Architecture, also called Ports & Adapters, organizes a system so that:

  • The core business logic is at the center.
  • All interactions with the outside world (UI, database, APIs, devices) go through ports.
  • External components connect via adapters.
  • The core is independent of frameworks, UI, and infrastructure.

This architecture is called β€œhexagonal” because the core can have multiple ports, like the sides of a hexagon, each representing a way external systems interact with it.

Key Concepts​

Core Domain (Application + Domain Logic)​

  • Contains business rules and use cases.
  • Does not depend on UI, databases, frameworks, or external systems.

Ports​

Ports are interfaces defined by the core that specify:

  • What the core expects from the outside world.
  • What the outside world can request from the core.

There are two types:

Port TypeDirectionPurpose
Inbound PortsOutside β†’ CoreExpose use cases (e.g., PlaceOrder, RegisterUser)
Outbound PortsCore β†’ OutsideDeclare dependencies (e.g., SaveOrder, SendEmail)

Adapters​

  • Adapters are implementations of ports:
  • They convert external input/output into a form the core understands.

Each external technology (web UI, REST API, database, message queue) gets its own adapter.

Adapter TypeRole
Inbound AdapterCalls inbound ports (e.g., REST controller, CLI handler)
Outbound AdapterImplements outbound ports (e.g., database repository, email sender)

Rules of Hexagonal Architecture​

  1. The core must not depend on adapters or frameworks.
  2. All communication happens via ports (interfaces).
  3. Dependencies point inward toward the core.
  4. External systems can be replaced without changing core logic.

Example: Online Ordering System​

Let’s design a simple Order Management System using Hexagonal Architecture.

Step 1: Core Domain & Use Case (Inside the Hexagon)​

Inbound Port (Use Case Interface)​

public interface PlaceOrderUseCase {
void placeOrder(int customerId, List<Item> items);
}

Outbound Ports (Dependencies)​

public interface OrderRepository {
void save(Order order);
}

public interface PaymentGateway {
void processPayment(Order order);
}

Domain Entity​

public class Order {
private int customerId;
private List<Item> items;

public Order(int customerId, List<Item> items) {
this.customerId = customerId;
this.items = items;
}

public double totalAmount() {
return items.stream().mapToDouble(Item::getPrice).sum();
}
}

Use Case Implementation (Application Service)​

public class PlaceOrderService implements PlaceOrderUseCase {
private final OrderRepository orderRepository;
private final PaymentGateway paymentGateway;

public PlaceOrderService(OrderRepository orderRepository, PaymentGateway paymentGateway) {
this.orderRepository = orderRepository;
this.paymentGateway = paymentGateway;
}

@Override
public void placeOrder(int customerId, List<Item> items) {
Order order = new Order(customerId, items);
paymentGateway.processPayment(order);
orderRepository.save(order);
}
}
  • Depends only on interfaces.
  • Has no knowledge of web frameworks, databases, or payment providers.

Step 2: Inbound Adapter (e.g., Web Controller)​

@RestController
public class OrderController {
private final PlaceOrderUseCase placeOrderUseCase;

public OrderController(PlaceOrderUseCase placeOrderUseCase) {
this.placeOrderUseCase = placeOrderUseCase;
}

@PostMapping("/orders")
public void placeOrder(@RequestBody OrderRequest request) {
placeOrderUseCase.placeOrder(request.getCustomerId(), request.getItems());
}
}

Role: Translates HTTP requests into calls to the core use case.

Step 3: Outbound Adapters (Infrastructure)​

Database Adapter​

public class JpaOrderRepository implements OrderRepository {
@Override
public void save(Order order) {
// Save order using JPA / SQL / NoSQL
}
}

Payment Adapter​

public class StripePaymentGateway implements PaymentGateway {
@Override
public void processPayment(Order order) {
// Call Stripe API
}
}

Role: Implements the core’s required ports using specific technologies.

How the Flow Works​

User β†’ Web UI β†’ Inbound Adapter β†’ Inbound Port β†’ Core Logic β†’ Outbound Ports β†’ Outbound Adapters β†’ External Systems

Advantages

  • Core logic is framework-independent
  • Highly testable (mock ports)
  • Easy to swap UI, DB, or external services
  • Promotes clean object-oriented design
  • Encourages SOLID principles (especially Dependency Inversion)

Disadvantages

  • More interfaces β†’ more code
  • Higher learning curve
  • Overkill for very small/simple projects

Comparison to Layered Architecture​

F

Layered ArchitectureHexagonal Architecture
Structure by technical layersStructure by business intent
UI β†’ Service β†’ Domain β†’ DBCore ↔ Ports ↔ Adapters
Dependencies flow downwardDependencies flow inward
Frameworks often in coreCore is framework-free