Is a Detail
One of the best practices for clean architecture is to delay some decisions as much as possible. Decisions related to which persistent storage to us or what kind of delivery mechanism you want to use should be delayed to the end.
How is it possible?
One quick reason could be these are easily replaceable. Yes, you read it right it is easy to move from a RDBMS to a NOSQL database if you layered your code right. How exactly do we create an architecture that gives us this flexibility? Keep reading to know.
Or the question could be just why?
Your design should be more related to what problem you are solving. Making decisions such as what kind of persistent datastore to use in the early stages might have an impact on the design of the core layers such as the entities and use cases. This approach of delaying the technology decisions also helps focus more on the client (by client I mean any organization, end users or the fellow employees that will use the software) aka The customer-first approach.
While designing systems sometimes we pay more attention to technologies that are trending right now. This might hamper the customer-first approach. It is really really important to choose the technologies that are more applicable to the customer and not the latest, cool, one’s.
Let us start with databases.
1 The Database is a Detail
Over the last half a century, how we store data has changed drastically. We have come a long way from the punchcards to today’s highly resilient distributed databases. All the while, if something has not changed, is that we still store things very similar to what we used to store on punch cards. Bits, Bytes, strings, numbers, etc. What has changed is how we store them or if I may say how we arrange it on the storage device. So if we are able to separate our business logic from the way how we store our data we will not be affected by the evolutions in storage technology
Let’s take a simple example of an account manager. Business functions that we will focus on are debit and credit,
If we are using a file-based store we would add a line with the amount and a descriptor showing if it is a debit or credit. If we are using a database we will do an insert query with the same details. If we are using a No SQL database like MongoDB we will still be adding a new JSON containing the same details Now if you may realize these business functions have no dependency on the type of data store we use. So we should not resort to creating these dependencies ourselves. Our business class should just call a function to add a new transaction unaware of the type of datastore we are using.
But….How can my business class not know what kind of datastore I am calling? It is a simple abstraction. Abstract away the details of your implementation from the business classes. In terms of a typical OOPM solution for this problem. Create an interface with the methods to add or delete records. Now the data store helper class implements this interface. The business class creates a reference of type interface and assigns the object of our datastore class to it. In this way, it is calling the same functions defined by the interface regardless of the datastore.
2 Website is a Detail, GUI is a Detail, CLI is a Detail
We should delay the decision for the presentation layer, our business layer decision should not depend on the way we are letting the users use the software. Today the client would want to have GUI, After some time he might want you to move all your processing to the server(Python, Java, Go) and want a lightweight website front. Now he has a necessity to make mobile apps and wants you to create a single backend that could simultaneously handle requests from the website, the mobile apps and the desktop app. At this stage any sane developer would create a decoupled architecture separating the business logic from the presentation layer(web, mobile, GUI/ Desktop). So why not do this from the beginning? In the initial days itself, separate the code that implements business logic from the code that manages the frontend. In this way you do not have to make a lot of changes to the core layers when the requirements changes. In turn we get a modifiable system that other developers working after you wont dread.
Implementation? We solve this problem the same way we solved the database as a detail problem. Use interfaces to create abstractions. Interfaces act as agreements that should not be broken and effectively helps our business implementation to be not dependent on the presentation layer
3 Framework is a Detail
Your code should not depend on frameworks. There are risks if you integrate a framework deep inside your code and your project needs to be rewritten if you want to change your framework. Risks:
- Locking in the framework as it gets really difficult to decouple your code from the framework if you have it integrated deep in your code
- You now need some advanced features which are just not available in this framework, maybe they just do not support it. As your project is deeply integrated in your business code getting rid of it is as good as rewriting the software
- The framework started catering to a completely different audience Solution: Do not deeply integrate the framework in you business code. Create specialized interfaces and helper classes to abstract away the details of the framework from your business code