Bending the Clean Architecture Principles
So you’re writing code using .NET, and you surely follow the Clean Architecture and its layered abstractions. Apparently and even though the concept was introduced long ago, this is getting very trendy now.
One of your arguments may be that you want your infrastructure to be entirely swappable, you want your application and core business logic to be independent from the database you are using right? The idea is not wrong, as they are hundreds of application relying on this concept. So what is the problem?
Designing an application
Applications are needed to solve a business need. When you design an app, you want it to fill a business and domain gap. Every app stores its data somewhere right?
So you have to carefully choose your infrastructure that corresponds to your application. Let’s say you’re choosing which database to use, how do you generally make the choice?
From my biased and opinionated experience, this choice comes from one of the following criteria's:
- Your team is already using SQL Server in this other project, so you will most likely start with it. Install Entity Framework, build your context and you’re done. It can be any provider really..
- You’re tied to the technology stack of the company you are in and you can’t just do as you please.
- You’re tied by the on premises or cloud constraints of your app (Pricing for example).
- Or you actually have this freedom to choose your most optimal database, isn’t that great?
It surely is, but let’s think this through… To persist your business domain, you will start to think on the ‘How should I store my entities’. This will also vary depending on the database you are using:
- If you’re using SQL, you will start drawing links and relations to navigate your fixed tables.
- If you’re using DynamoDB or Cosmos, you will think about your partition and sort keys, how the entities should be grouped and distributed.
- If you’re using MongoDB or anything else really, you will find yourself between the two.
My point is, even though there is a lot of database providers, your plan to concept your database may vary a lot depending on the infrastructure you are using. And yes your domain should not worry about those changes so let’s abstract it. This is when the famous Repository pattern come in handy.
You abstracted your famous repository, which contains another abstraction of your database provider (EF Core for example). The benefit? you can swap now the inner abstraction with Dapper… but why would we do that?
The abstraction itself is great, but why do we need to implement it even if it’s not that useful? With that thinking, we should abstract the ILogger interface, or any other module in our system. So that we can ‘swap it when needed’, even when it’s not actually needed…
You’re actually smarter than this, you need to switch from SQL to Cosmos for a cloud migration on Azure. So this is actually very handy right? Well not really:
- The repository interface is a contract that the application is using and cannot be changed. If it changes than you actually implemented all of this for nothing.
- Not changing it is also a problem, even if you’re app is not tied to the persisted entities, storing and optimizing transactions is highly dependent on the provider. You will store, retrieve and perform CRUD actions differently when switching from SQL to Cosmos. And to honor this famous contract that you cannot break, you will find yourself writing a big mud of code to transform and fetch your entities.
Projecting over time and supposing you’re in a Agile environment where iterating and adding functionalities is mandatory, this bug ball of mud will grow to an unmaintainable or manageable code.
The ‘SMART’ solution
A very smart idea will pop up in your head. The app is really slow and need optimization. This bottleneck will guide to write the entire thing from scratch while optimizing and accommodating to a new schema and a new DB provider. Everything ends well you saved the day!
You didn’t actually save the day, you waited and built a big ball of mud that created maintainability and performance issues. It most certainly took you double the time to rewrite with all the functionalities you added over time.
Knowing how to abstract your repository is great, but this should not be a standard where you implement everywhere. Most of the DB clients are already an abstraction so why add another layer on the top? If you’re in a Microservices architecture, your bounded context should not be big enough to rewrite it. 1 or 2 entities are involved only.
Also, it is not that you change your provider everyday, you chose or are bound to a provider and when you do change (which you rarely, RARELY do), it is really useful to see what will break and make the most adequate and optimal change for this new source.
Don't overuse this pattern for the sake of clean code.