In most scenarios we can defer the implementation of external details and still validate the application behavior. If your answer is yes for any of the next questions, you are probably dealing with peripheral details:
- Does the application needs an database to persist state?
- Does the application requires an User Interface?
- Does the application consumes an external API?
These are common external details which can be mocked, faked or their concrete implementation be replaced for different reasons. I suggest to defer their implementation while you discover more of the domain.
Visual Studio makes easier to add libraries for Reflection, Serialization, Security and many others Nuget packages in our projects. The problem begin when we add these libraries to our Application and Domain. These libraries are just details and should be out of the Application. What we can do?
- Stop writing classes with inheritance from frameworks.
- Stop going for shinning objects.
- Focus on the business rules, make them clear on your Application and Domain Layers.
- Don’t fall into tooling traps.
- Create the appropriate abstraction for these peripheral concerns.
For didactic reasons we call these details as Ports and Adapters in Hexagonal Architecture style.
Business Rules and Use Cases
The business rules are the fine grained rules, they encapsulate entity fields and conditions. Also the business rules are the use cases that interacts with many entities and services. They together give create process in the application, they should be sustained for a long time.
In the DDD age, we have patterns to describe business with Entities, Value Types, Aggregates, Domain Services and so on. They are a perfect match with Hexagonal Architecture. Moving on, there are design principles that must be clear before implementing Hexagonal Architecture style.
Dependency Inversion Principle (DIP)
In the next article the DIP was applied when decoupling our Application Services from the Repositories. And this principle was used to decouple many other things in our Application.
What are the most important examples in there?
- The DepositService is the High-level module that do not depend on database details, instead it depends on IAccountRepository abstraction.
- The IAccountRepository is the abstraction that do not depend on database details.
- The AccountSQLRepository is the low-level module that depends on IAccountRepository abstraction.
To clarify the idea watch the next picture with the before and after applying DIP Concept:
- On the left side of the next picture we find in blue an Layered Application where the DepositService depends on AccountSQLRepository.
- And on the right side in green, by adding an IAccountRepository and applying DIP then the AccountSQLRepository has your dependency pointing inwards.
The following listing of DepositService shows an implementation.
That is the main idea behind Hexagonal Architecture, every time our application requires an external service we implement adapter behind an abstraction.
Separation of Concerns (SoC)
Our application requires some external capabilities but the application is not concerned about their implementation details, only their abstractions are visible to the application layer. We apply SoC by creating boundaries around the Adapters and by allowing them to be developed and tested in isolation. Usually, we have different packages for each Adapter. We could have an specific Adapter for an SQL Database and an specific Adapter for Azure Storage which could be replaced with little effort. That is the idea behind the Hexagonal Architecture, keep the options open as long as possible and the ability to rollback if necessary.
Hexagonal Architecture Style Characteristics
With this style we have:
- An independent Business Domain to embody the small set of critical business rules.
- Application Services to implement the use cases.
- Ports to get the input.
- Adapters providing implementations of frameworks and access to databases.
- Externally the user, other systems and services.
Here's another picture about what's the Hex Architecture with shapes.
- The blue rounded circle shape at the center is the Domain and there are reasons for it. Every business domain has its own rules, different specifications from each other, that is the reason of its undefined shape. For instance, I designed our Domain Layer with DDD Patterns.
- The application has an hexagonal shape because each of its sides has specifics protocols, in our example we have Commands and Queries giving access to the Application.
- The Ports and Adapters are implemented outside of the application as plugins.
- Externally we have other systems.
The direction of the dependencies goes inwards the center, so the Domain Layer does not know the Application Layer but the Application Layer depends on the Domain, the same rule applies to the outer layers.
Let’s describe the Dependency Layer Diagram below:
- The domain is totally independent of other layers and frameworks.
- The application depends on Domain and is independent of frameworks, databases and UI.
- Adapters provides implementations for the Application needs.
- The UI depends on Application and loads the Infrastructure by indirection.
We should pay attention that the Infrastructure Layer can have many concerns. I recommend to design the infrastructure in a way you can split it when necessary, particularly when you have distinct adapters with overlapping concerns. It is important to highlight the dashed arrow from the UI Layer to the Infrastructure layer. That is the where Dependency Injection is implemented, the concretions are loaded closer to the Main function. And there is a single setting in a external file that decides all the dependencies to be loaded.
The Application Layer
To make simpler the Application Layer implementation, I split it in two stacks: one for the transactions and other for the queries.
- When the user sends an Deposit Input, it goes to the transactions stack, it is converted into a Command that goes through the DepositService, uses the Entities to enforce the business rules and the transaction is finally persisted in a database by an adapter.
- The other stack is tinnier and implemented by the Adapters. It is used only for querying view objects.
With this approach we avoid the degradation of our domain because we don’t need to represent every Query Results into Entities.
Use Cases Components
It is very important to organize the Application Layer with the use case vocabulary. I recommend one folder for each use case, as we have in the following example:
- Command (DepositCommand.cs)
- Use case Interface (IDepositService.cs)
- Use case Implementation (DepositService.cs)
- Command Result (DepositResult.cs)
With this approach we have an application design that supports new use case implementations with fewer changes in existing code base. This keep the work effort for new use cases implementations constants along the sprints in an Agile methodology.
For the Query side, in the Application Layer we have only an small interface. And in the Infrastructure Layer we have the Adapter implementation.
By having an guarantee that the query side does not make changes in state. We can take advantage of better solutions for reading. For instance we can use caching, segregated databases to boost performance and it could be done inside the Adapter.
A Port is an way an Actor can interact with the Application Layer. The role of the Port is to translate the Actor’s input into structures the Application Services can understand. For instance a Port could be an Web Form, an Console App or another system. For this article the Port supports the REST protocol and was implemented using WebApi framework.
The WebApi has Controllers that do not depends on Application Services implementation, its easy to mock this services.
We segregate Port Components by use cases, for the Deposit use case:
- Request (DepositRequest.cs)
- Controller + Action (DepositController.cs)
- Model (Model.cs)
You can find the complete solution here: Github