Skip to main content
pragmatic clean code minimizing cognitive load in production java

The Core and the Shell

3 min read Chapter 24 of 25
Summary

Hexagonal Architecture separates pure domain logic (The Core)...

Hexagonal Architecture separates pure domain logic (The Core) from I/O and frameworks (The Shell). The Core contains business rules with no external dependencies; the Shell handles persistence, UI, and external systems via adapters implementing Core-defined ports, ensuring maintainability and testability.

The Core and the Shell

Introduction to Hexagonal Architecture

Hexagonal Architecture, also known as Ports and Adapters, is a design pattern that isolates the application core from external concerns using interfaces (ports) and implementations (adapters). This architectural style is crucial for maintaining a clean separation of concerns, where the core of the application contains pure business logic, domain entities, and use cases, devoid of any knowledge of frameworks, databases, or UI.

The Core

The Core is the central part of an application, containing the domain model, which incorporates both behavior and data, representing the business rules of the application. It should have zero dependencies on the Shell, ensuring that the application’s core logic is independent of external factors. The Dependency Inversion Principle plays a significant role here, where high-level modules (The Core) do not depend on low-level modules (The Shell); both should depend on abstractions.

The Shell

The Shell, on the other hand, is responsible for handling I/O operations, user interfaces, database persistence, and third-party framework integrations. It is the outer layer of the architecture, and its primary function is to convert external technical formats (JSON, DB Rows) into domain objects that can be understood by The Core. The Shell must depend on The Core, not the other way around, to maintain the integrity of the architecture.

Ports and Adapters

Ports are interfaces defined in The Core that specify how the application wants to interact with the outside world. Adapters, which reside in The Shell, implement these ports, providing the actual interaction with external systems. This separation allows for a clean and testable architecture, where The Core remains untouched by changes in the external world.

Example Implementation

Consider a simple e-commerce application that needs to save orders to a database. The Core would define a port (interface) for saving orders, while The Shell would provide an adapter (implementation) that uses a specific database technology to save the orders.

// THE CORE: Pure Domain Logic
public record Order(OrderId id, List<LineItem> items) {
    public Money calculateTotal() {
        return items.stream()
                    .map(LineItem::price)
                    .reduce(Money.ZERO, Money::add);
    }
}

// THE CORE: Port (Interface)
public interface OrderRepository {
    void save(Order order);
}

// THE SHELL: Adapter (I/O Implementation)
public class SqlOrderRepository implements OrderRepository {
    private final JdbcTemplate jdbc;
    
    @Override
    public void save(Order order) {
        // SQL logic trapped in the Shell
        jdbc.update("INSERT INTO orders...");
    }
}

This example demonstrates how the application’s core logic remains independent of the database technology used, allowing for easier switching between different databases or even testing the application without a real database.

Conclusion

In conclusion, separating the application into The Core and The Shell, using Hexagonal Architecture, provides a robust and maintainable system. By keeping the domain logic pure and independent of external concerns, developers can ensure that their application remains flexible and adaptable to changing requirements. The use of ports and adapters enables a clean separation of concerns, making it easier to test and maintain the application.

Sources

No external sources were used in this section.