The Core and the Shell
SummaryHexagonal Architecture separates pure domain logic (The Core)...
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.