Part I: Code Architecture Theory

Iván Guardado
Audiense Engineering
6 min readJul 2, 2018

--

Hexagonal Architecture, Clean Architecture, Onion Architecture… terms you have probably seen before but perhaps, like me, don’t fully understand what they mean or the differences between them. The good news is that they are techniques surrounding the same issue. When organizing the application code it is important to isolate the business code from the infrastructure. What is the business logic?

This is the question I asked myself repeatedly when I was delving into this topic. A quick summary of business logic is the use of cases that an application allows, keeping all the infrastructure implementation details aside. For example:

How to create a new user

  1. Check there are not any users with the same email
  2. Validate that the password is strong enough
  3. Create the user
  4. Send the welcome email

The code that is in charge of this flow is met with business logic code which is also known as domain application code. However details about how the user is created (DB) or how the welcome email is sent (Mandrill, SES, …) is considered infrastructure code. The reason for this separation is clear: changing the way of sending emails or saving users should not modify the logic of our applications must do’s.

How are we be able to change the repository using MySQL and by using others called MongoDB? MongoDB does not affect the business logic and can keep running without having to be modified. If you are familiar with the Mongo syntax you will realize that this is totally different to the common SQL.

The solution to this is that the domain code expose interfaces. This sets a communication channel around the outer layers. The code has the ability to understand the exposed interfaces, making the external layers adaptable (this is the reason that the approach is also known as Port & Adapters).

In this example, we look at an application domain that uses case which requires the implementation of a UserRepository. There could be any number of uses for it, for example we could have the MySQL implementation running production and another one in Mongo to run locally. It is important to remember that the createNewUser feature remains constant because they are in the same language. The business rules are still the same so we should not have to touch any code.

At this point, you could be asking yourself how to develop the createNewUser that accepts any repository implementation, or how to do this in NodeJS if we don’t have interfaces in the language. I touch upon this later, but let’s continue introducing other basic architecture concepts.

Domain modeling

The separation between domain and infrastructure is the most natural, inside our domain we have to model all the business logic in the best possible way, and for this we need to set some rules. When I say the best possible way I mean that the code meets all the SOLID fundamentals so it’s easy to read, to change and especially, to test. In the domain we can differentiate the code in three categories or layers: domain objects (also Core Domain), domain services and application services.

Domain Objects

These are classes, value objects, events, types, etc that compound the domain core. One of the most important types of domain object are the entities. Entities are objects with a unique identity and represent an important part of the business. For example if you are modeling an e-commerce solution some entities could be a product, a customer, an invoice, etc…

Domain Services

A domain service defines a business logic that has too much complexity to live in an entity. Continuing with the e-commerce example, calculate the shipping costs or calculate the invoice total that could be considered a domain service since they are often complex processes.

Application Services

These are the real use cases that define the application and serve as the entry point for the external layers (framework) to the business logic. If we want to connect our domain with a web server like Express, we can call upon the application services from controllers, never from domain services or entities.

This kind of service should coordinate the flow between different domain services, entities and infrastructure.

Application services are also known as actions, command handlers or use cases. I’ll refer to them as actions from here on.

Browsing the actions folder

Every use case can only be represented in code as an action, and if we keep the rule of having every action in its own file, we can understand what the application can really do just by browsing the actions folder.

Action Flow Example

In this Sandro Mancuso’s graph we can see how an action could be modeled to make a payment in an application. Look at how the infrastructure dependencies are defined as interfaces.

Another interesting concept here is the Outside-in approach. If you look at the graph, as you move to the left, the terminology moves closer to business terms. However as you move towards the right the language becomes more and more technical. This way we can use the TDD technique to create tests for the action and define the behaviour to implement it gradually.

Dependency Rule

Once we’ve seen all the layers of code as a part of our domain code it must be divided, based on their hierarchy like a set of concentric layers where the closest to the center represents the business and most external layers represent infrastructure implementation details.

To keep the code decoupled we need to follow the dependency rule which ensures the internal layers remain independent of the external ones. This means an entity can’t know or use a domain service, a domain service can’t use an application service and so on… However the inverse is allowed, for example, an applications service orchestrates the flow for domain services and entities, or a framework’s endpoint will call for an application service.

Under certain scenarios we could need to use infrastructure dependencies inside our domain. This is solved by exposing interfaces (ports) and inverting the dependency flow, satisfying the dependency rule. This technique is known as inversion of control (IoC).

The second case is much more flexible because it allows for the dependencies to be specified from the outside so they can be changed according to the context. This makes it more testable as we are able to fake the repository to simulate multiple scenarios and avoid having a real database.

Having said that this code shouldn’t be used in your application because mixing dependencies and parameters in the function arguments is not considered good practice. We’ll see a better way to do this in the part 2, avoiding to pass dependencies at every stage.

--

--

I love solving business problems applying technology solutions in a smart and scalable way. http://ivanguardado.com