Introduction
Hexagonal Architecture, also known as Ports and Adapters, is an architectural pattern that promotes the separation of concerns in software applications. The pattern is based on the idea of a hexagon, with the core of the application at the center and different layers surrounding it. The core represents the domain logic of the application, while the outer layers are responsible for interacting with the outside world.
Domain-Driven Design (DDD) is a software development approach that focuses on the domain model of the application. The domain model represents the real-world entities, their attributes, and the relationships between them. The goal of DDD is to create a shared understanding of the domain among all stakeholders involved in the project.
Together, Hexagonal Architecture and Domain-Driven Design (DDD) can be used to build applications with complex and isolated domain that can absorb changes quickly and keeping the code with high quality
Hexagonal Architecture
The Hexagonal Architecture pattern is composed of three layers:
The Domain Layer: This layer contains the business logic and represents the core of the application. It should be independent of the infrastructure and user interface.
The Infrastructure Layer: This layer contains the implementations of the interfaces defined in the Domain Layer. It is responsible for providing the necessary tools and resources for the application to run, such as databases, messaging systems, and web frameworks.
The Application Layer: This layer is responsible for coordinating the interaction between the Domain and Infrastructure Layers. It contains the use cases, which are the specific actions that the application can perform.
Domain Driven Design
DDD is composed of several patterns that can be used to implement the domain model, such as:
Entity: This pattern defines an object that has an identity and a lifecycle. It represents a real-world entity, such as a customer, an order, or a product.
Value Object: This pattern defines an object that has no identity and represents a value. It is used to model attributes of entities that do not change, such as the price of a product.
Aggregate: This pattern defines a cluster of entities and value objects that are treated as a single unit of consistency. It is used to enforce business rules and ensure consistency in the domain model.
Repository: This pattern provides a way to store and retrieve domain objects from a database or other persistence mechanism. It separates the domain model from the persistence logic.
Factory: This pattern provides a way to create domain objects with complex initialization logic. It encapsulates the creation of objects and hides the details from the rest of the application.
Service: This pattern provides a way to encapsulate domain logic that does not naturally fit into an entity or value object. It represents an operation or a process that is performed on the domain model.
Domain Event: This pattern represents an event that has occurred in the domain model. It allows different parts of the application to react to changes in the domain model.
Hexagonal Architecture and DDD
Using Hexagonal Architecture and DDD together can bring several benefits to software development projects:
Separation of concerns: By separating the application into different layers, and by using DDD to define the domain model, the application becomes more modular and easier to maintain.
Testability: Separating concerns also makes the application easier to test, as each layer can be tested independently of the others.
Flexibility: By separating concerns, the application can be adapted to different environments or platforms without having to rewrite the entire application.
Alignment with business requirements: DDD helps to create a shared understanding of the domain among all stakeholders involved in the project. This leads to a better alignment between the business requirements and the technical implementation.
However, there are also cases when Hexagonal Architecture and DDD should not be used:
Simple applications: For small and simple applications, using Hexagonal Architecture and DDD may be overkill. These patterns are best suited for complex applications with a rich domain model.
Tight coupling between infrastructure and domain: If the domain model is tightly coupled with the infrastructure, it may be difficult to separate them into different layers. In this case, using Hexagonal Architecture and DDD may not be practical.
Conclusion
In conclusion, Hexagonal Architecture and Domain-Driven Design are powerful techniques that can help you build maintainable, scalable, and flexible software applications. By separating concerns, emphasizing the importance of the domain model, and creating a shared understanding of the domain, you can build applications that are easier to modify, test, and maintain, and that align better with the business requirements.
Example with Golang
The example application built using Hexagonal Architecture and DDD is a simple e-commerce system that allows users to add books from their shopping cart. The application is organized into separate layers for domain, application, and infrastructure, with each layer following the principles of DDD.
The example includes a basic implementation of the repository pattern, entity pattern, value object pattern, aggregate pattern, factory pattern, and a use case implementation for adding a books to a cart and other for listing books.
All the application source code can be found in the following github repository: https://github.com/prbpedro/ddd-hexagonal-arch-go.
The application folders structure is:
cmd/
main.go (main program)
internal/
application/
initializer/
in_memory_database_application_initializer.go (Initializer/port implementation to create all use cases and it's dependencies using an in memory database)
usecase/
add_to_cart_use_case.go (Use Case/port implementation to add a book to the user cart)
serach_books_use_case.go (Use Case/port implementation to search books)
domain/
entity/
book.go (Book Entity)
cart.go (Cart Entity and Cart item aggregate)
valueobject
price.go (Price Value Object)
ports/
initializer
application_initializer.go (port|interface definition to initialize the use cases and its dependencies)
input
add_to_cart_input_port.go (port|interface definition to Add a book to a user cart)
search_books_input_port.go(port|interface definition to search books)
output
cart_repository.go (Cart repository port interface)
book_repository.go (Book repository port interface)
infrastructure/
repository/
book_repository.go (output port implementation of cart_repository with in memory database)
cart_reository.go (output port implementation of book_repository with in memory database)
If you found the content valuable and want to stay updated on future posts related to microservices patterns, design patterns, observability, and more, please follow me on LinkedIn, Substack, and Twitter.
You can find me on LinkedIn at https://www.linkedin.com/in/pedro-ribeiro-baptista/, where I regularly share industry insights and connect with fellow professionals.
On Substack, you can find my profile at https://prbpedro.substack.com/ , where I publish articles and essays on software engineering and related topics.
Finally, you can follow me on Twitter at https://twitter.com/prbpedro1, where I share interesting articles and insights on software engineering and other tech-related topics.
Thank you for your support, and I look forward to connecting with you on these platforms!