Hexagonal Architecture
# Swappable data sources
- #monolith allowed for rapid development and quick changes, while the knowledge of the space was non-existent.
- Decompose the #monolith: not geared by performance issues - but with the settings of boundaries around all of different domains and enabling a dedicated team to develop domain-specific services independently.
- Leverage some of the data from the monolith at first as it was still the source of truth, but be prepared to swap those data resources to new microservices as soon as they came online.
# Leveraging Hexagonal Architecture
- Needed to support the ability to swap data sources without impacting the existing business logic.
The idea of Hexagonal Architecture is to put inputs and outputs at the edges of the system design. Business logic should not depend on whether we expose a REST or GraphQL API, and it should not depend on where we get data from - a database, microservice API expose via gRPC or REST, or just a simple CSV file.
- Isolate the core logic of our application from outside concerns. Having our core logic isolated means we can easily change data source details without a significant impact or major code rewrites to the codebase.
- Testing strategy - the majority of our tests can verify our business logic without relying on protocols that can easily change.
# Defining the core concepts
# Entities
- domain objects, they have no knowledge of where they’re stored (unlike Active Record in Ruby on Rails or the JPA).
# Repositories
- interfaces to getting entities as well as creating and changing them. They keep a list of methods that are used to communicate with data sources and return a single entity or a list of entities.
# Interactors
- classes that orchestrate and performs domains actions - think Service Objects or Use Case Objects. They implement complex business rules and validation logic specific to a domain action.
With the three types of objects described above, we are able to define business logic without any knowledge or care where the data is kept and how business logic is triggered. Outside the business logic are Data Sources and Transport Layer:
# Data Sources
- adapters to different storage implementations.
- Might:
- an adapter to SQL database (an Active Record class in Rails or JPA in Java)
- elastic search adapter
- REST API
- or even an adapter to something simple such as an CSV file or a Hash
- A data source implements method defined on the Repository and store the implementation of fetching and pushing the data.
# Transport Layer
- can trigger an interactor to perform business logic
- treated as an input for our system.
- The most common transport layer for microservices in the HTTP API Layer and set of controllers that handle requests.
- By having logic extracted into Interactor, we are not coupled to a particular transport layer or controller implementation.
- Can be triggered not only by a controller, but also by an event, a cron job, or from the command line.
In Hexagonal Architecture, all dependencies point inward - core business logic doesn’t know anything about the transport layer or the data sources. Still, the transport layer knows how to use interactors, and the data sources know how to conform to the repository interface.
# Swapping data sources
# Hiding data source details
# Testing strategy
- test the application at three different layers:
# Interactors
- leverage Dependency Injection and mock any kind of repository interaction.
# Data sources
- to determine if integrate correctly with other services, whether they conform to the repository interface, and check how they behave upon errors.
- Try to minimize the amount of these tests.
# Integration tests
- go through the whole stack, from Transport/API layer, through interactors, data sources, and hit downstream services.
- wired everything correctly.
- If a data source is an external API, we hit that endpoint and record the responses (store them in git), allowing test suite to run very fast on every subsequent invocation. For example: https://github.com/vcr/vcr
- don’t do extensive test coverage on this layer - usually just once happy case and failure scenario per domain action.
- don’t test repositories as they are simple interfaces that data sources implement, and rarely test our entities as they are plain objects with attributes defined, do it when they have additional methods (without touching persistent layer).
- contract testing
# References
https://netflixtechblog.com/ready-for-changes-with-hexagonal-architecture-b315ec967749