When reading Clean Architecture by Robert Martin, I came across a chapter of the same name. In it the author describes this “idealized” architecture structure where business and application logic are at the center of the diagram with outward rings surrounding it describing interactions with various frameworks and IO devices.
This architecture can also be known as Hex Architecture or Ports and Adapters Architecture, all having a similar design where core business logic is independent of the IO interfaces surrounding it allowing for architectures that are independent of frameworks, ui, and databases.
By creating a clean architecture you are able to establish a proper domain for your application. The domain is a business object and is not tied to a specific technology and therefore should be wrapped in an abstraction or library to be consumed by technologies. Currently most of the microservices we develop have code repetition scattered throughout in the form of business logic and domain entities. This duplication also results in changes in multiple places and adds complexity to an already complex system. By pushing the business logic into its own library we can provide an abstraction to our system and allow for multiple IO devices to work and provide the ability to change things like database or clients if need be.
For example, let’s assume that our entire system runs on a JVM. We have built out the core business logic into a Java library (jar) with various interfaces and abstract classes to describe how the business rules work. We can then make decisions independent of that logic such as, should this be a Postgres or MongoDB? For the API should we use Spring Framework in Java or move to a Scala Framework. Would the consumer work best as a Clojure app? In the end it doesn’t matter because they can all use the core library. Each can also be tested independently and the core library can focus on testing business logic instead of implementation details while libraries such as the database layer can focus on unit tests specific to that database.
What would a Clean Repository Look Like?
First and foremost a Clean Repository should be named after the domain it is supporting and not the applications it holds. Since the whole point is to build applications around the domain the repository should reflect a similar structure by following a similar pattern to a Clean Architecture in that we separate the different components into different libraries.
Each library provides pieces needed to build the applications to support the domain
- core - this would house the core business and application logic needed for the specific domain you are building. This package should only provide processes and no libraries specific to any external calls to databases or apis.
- database - this directory would implement specific repository needs to the core library. It is the responsibility of this library to fulfil the abstractions and interfaces that are needed by the core system for other systems to work.
- clients - similar to a database a client directory would fulfil any abstractions needed by the core directory to get needed information by calling external apis
- apis/workers - These would be specific applications that tie the core and client/database together.
This may seem a bit broad but with each type of language the implementation would be different. In Java core could be done through interfaces and abstract classes while in Go it could be done through interfaces. APIs, workers, clients, and databases are all subject to leveraging different frameworks.
How should Core be built?
The core directory should be structured around the domain and its entities this means deviating from the typical MVC folder structure we are used to seeing. Instead of having a model and service directory I would suggest having directories around domain entities such as user or tracking. These entity directories would then house files that house abstract model definitions, services with interfaces, and interfaces for client and database interactions.
Sure, this can still be accomplished using the old directory structure but by switching to an entity focused directory structure you can easily monitor the different entities and determine logical boundaries for establishing subdomains which inevitably could be another microservice. Extracting a single directory then to be its own core would make the transition smoother than trying to dig through various directories finding implementations and possibly untangling them. It also forces you as the developer to try to understand how these entities interact with one another. If you communicate solely through interfaces this extraction should be fairly painless. Implementing a new microservice based off of this extracted package would then be implementing the APIs, workers, clients, and databases.
In the end we can achieve a more flexible architecture by adopting some of these principles found in a “Clean Architecture” but in order to implement this type of architecture we need to start looking how we manage our code repositories. Instead of centralizing them around specific applications we should be focusing on the domains they support and protecting the business logic. Once you’ve established the core logic you can use it in many different applications at a rapid pace because you are no longer replicating logic. You can then pick and choose pieces that you need to build the apps you want.