Was reading up about Netflix and their use cases that requires their components to be loosely coupled, the Hexagonal Architecture, or as some other calls it the port and adapter architecture, serves that aim. Why is it called hexagonal? This is how the architecture will look like.
Hexagonal Architecture or ports and adapters architecture aims to create loosely coupled application components that can easily connect to their software engineer by means of ports and adaptors. This makes the components exchangeable at any level and facilitates test automation. The original intent of Hexagonal Architecture is to allow an application to equally be driven by users, program, automated tests, and to be developed and tested in isolation from its eventual run-time devices and database. One of the benefits to this architecture is to isolate the core business and automatically test its behaviour independently of everything else. Since the application core logic is in the middle, whatever users or programs that wishes to interact with the application core has to be integrated through ports and adapters.
Three main parts of the Hexagonal Architecture
For the Hexagonal Architecture, there are three key principles:
- A clear segregation of application (left) side, domain center, infrastructure (right) side
- Dependencies points inwards, both application and infrastructure depend on domain
- The boundaries are dictated by the use of ports and adapters.
The application code is segregated to three distinctive parts, the interaction with actors called the application side, the logic called the domain, and logic that depends on external services such as the database called the infrastructure side.
The domain side will be predominantly filled with business domain vocabulary and pure business logics. Ideally a domain expert that does not know how to code should be able to point out inconsistencies in the business logic.
The infrastructure side is where the essential parts that allows the logic to work will reside. This side contains code that interacts with database, makes calls to file system, handles HTTP calls to external services that your application depends on.
Put input and outputs at the edge of our design. Busines logic should not depend on whether we expose a REST or a GraphQL API, and it should not depend on where the data was retrieved from - a database, microservice API exposed via gRPC or REST, or just a simple CSV file.
This clear segregation allows a separation of concern. You can now deal with logics of any of the three parts almost independently of the other two. Secondly, your business logic can now be tested without taking on the cognitive load of the rest of the program and allows developer to fully grapple the business logic that is required to be delivered. Thirdly, now you can test the whole domain individually, or the integration of application with domain independently of the infrastructure side, or domain with infrastructure independently of the application side. With good boundaries, you are able to write test cases to test business logics without any reliance on data source protocols.
Notice how the arrows are pointing inwards, implying a dependency on the interfaces
The adapters all resides in the domain layer, and the domain logic will interaction solely with these adaptors. Since the domain layer consist only the domain logic, the technical implementation details such as the origin of data source such as CSV file, database, or external service does not matter and is abstracted away. The idea of interfaces in which the domain expert can simply say: IRequestPoem. You can read the interfaces from a first person perspective. So IRequestPoem becomes I request poem and this is something a domain expert with no coding knowledge can understand.
Now in the technical implementation, the infrastructure class will inherit the interface defined in the domain and implement it. Domain does not depend on anything, everything depends on domain. The infrastructure side depends on the domain for the adapter that will fit the domain logic. Application side requests for an object that is dependent on it first being obtained from the infrastructure side. For example, if console wishes to display poem, it has to first obtain poems, which is an interface between domain and infrastructure.
Note that the boundaries of Domain are isolated with interfaces and these interfaces or ports are all defined by the Domain. Domain will define what port to use (for example of electrical appliance: 3 pin, 2 pin, european pin, american two flat pins). Now based on this port, we can create an adapter to fit. It can be either hard coded data source during unit test, or a real database in an integration test. Whichever the case, the domain is not impacted by this switch.
The interfaces are your ports, and you need to create adapters to find those ports
Once you segregate these three parts, you are now free to do whatever you want within each parts.
How does this architecture actually looks like in an application?
The instantiation order is typically from the right to the left.
- We instantiate the infrastructure side adaptor class with the interface that is created by the Domain center.
- We instantiate the domain class that will be drive by the application, then we add in the instantiated infrastructure side class instance of the adaptor into the constructor of the domain class.
- Then we instantiate the application side by injecting the domain class instance into the constructor of the application instance.
The infrastructure and domain side are able to be weakly coupled with the use of inheriting interfaces that are well defined by the domain. As long as the function signatures are consistent, it does not matter what the implementation of the inherited class does. So this is said that the infrastructure side depends on the business (domain) for its definitions. Because without the interface being defined by the business, the domain will now depend on the infrastructure for its definition; thus creating a strong couple.
We want this instead:
With this approach, we also satisfy the famous D in the SOLID principle, Dependency Inversion principle that indicates two points:
1. High level modules should not depend on low level modules; both should depend on abstractions.
2. Abstractions should not depend on details. And to achieve this, you need to introduce an abstraction that decouples the high-level and low-level modules from each other.
- Based on top of the O of the SOLID principle, Open/closed principle, the interface is closed for modification but open for extensions by providing a new interface implementation.
- Following this principle to implement your high level business modules’ logic decouples the dependency of low technical implementation details from the business logic itself.
Reference for SOLID
The dependency inversion principle, where Domain is the high level module, that should not be concern with the implementations of the low level modules.
source: Netflix tech blog
Now the next part is why then does the application needs to have an interface to inherit and apply? Why cant the application just interact directly to the business core logic?
The answer is found in another SOLID principle, Interface Segregation principle (ISP). The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use.
ISP splits interfaces that are very large into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them. Such shrunken interfaces are also called role interfaces.
So with the use of interfaces, you are now able to draw the boundary of your domain logic.
Testing is part of the reason for Hexagonal Architecture
Any code deserves to be tested. A really good insightful article about the test pyramid. There is nothing much I wish to go into details for this section but note how this implementation allows a separation of concern by drawing up boundaries with interfaces. Now your test suites can inherit those same interfaces and create an adapter to perform testing as well.
- Understand that the Hexagonal Architecture is only an example of The clean Architecture and we will definitely delve deep into this in another article.
- Read more about Integration-seperation principles. Will savour this learning for another time.