I am trying to implement the clean architecture and my current understanding of it is that it is meant to increase loose coupling and database independence mostly through dependency injection & dependency inversion. I am currently using EF Core on the infrastructure layer with Masstransit(Mediator & Messaging) on the application layer. I use a Generic Repository that sits on the Infrastructure Layer where the EF related methods like "ToListAsync" and "FindAsync" are expressed and i access them from the Application Layer through an interface. My LINQ specification code also sits on the application layer.
This all made sense as long as i assumed that the reason to move the EF dependency to the Infrastructure layer was that i was making my code framework and database independent and that LINQ would work with other databases or with another DAL library. It just so happens that i recently decided to replace EF with Dapper and all this talk about database independence and loose coupling starts to make little sense to me. I still have to rewrite everything from the ground up since LINQ queries can’t be used with Dapper which means my extension methods and other abstractions i built are now useless. And besides, there are many other very relevant (NoSQL) databases that don’t have a mapping with LINQ.
Now here’s my question. How to ensure that my Core project (Domain and Application Layer) stays agnostic of anything that relates to the persistence layer. That not only includes EF but also LINQ queries.
3
Answers
Maximizing business separation from storage layer technologies has always been one of my concerns.Just try to change how you define your repositories: Take a look at a very simple library I use https://github.com/mzand111/MZBase. The MZBase.Infrastructure (also available as a nuget package) is a .Net standard library which contains some base interfaces and classes.
Also to handle paging, sorting and filtering just using basic data-types I have developed another library https://github.com/mzand111/MZSimpleDynamicLinq which provides two main classes to use in your repositories: LinqDataRequest and
LinqDataResult. So if you take a look at ILDRCompatibleRepositoryAsync it has minimum technology dependence and implementing this interface is possible in most of the ORM technologies.
The idea of the clean architecture is to separate your business logic that much from any external service, DB or IO that you do NOT have to rewrite anything in your business logic if you want to replace one technology by another.
if you still have to rewrite parts of your business logic then it is obviously not separated properly. If your LinQ statements only work if the implementation is EF then the interface adapter is not really an adapter and the business logic is making an assumption about the implementation of the DAL.
Additionally this thread might be interesting in this context: How can Clean Architecture's interface adapters adapt interfaces if they cannot know the details of the infrastructure they are adapting?
You almost got it, but instead of concluding that you did something wrong, you concluded that there’s something wrong with Clean Architecture. But, as you say, why would you make it seemingly independent but not really? Well, you don’t! You have to really make it independent.
There are two important things to note from the description of your implementation:
Using (EF Core) LINQ in the application layer. Querying a DB using LINQ is a very specific EF thing. The fact that you managed somehow to hide part of the expression (ToListAsync) in the infrastructure layer, doesn’t mean that you have abstracted anything. Your application code is custom made for EF and EF only.
You are using a generic repository. A generic repository, even behind a (single) interface, is not Clean Architecture friendly. In clean architecture it’s the Core or business logic code which defines a very concrete interface for each specific scenario. As all scenarios are different, you can’t create a (single) generic repository interface which covers them all without forcing some scenarios to depend on functionality that they don’t need. This, not only is not SOLID, but also it can complicate your life a lot. For example, as Clean Architecture promises, you should be able to replace your DB, but not as a big bang change. You should be able to move your Products (for example) to MongoDB while leaving the rest of the application in SQL Server. Of course, if all your data access is behind a generic repository, you are forced to change everything at once. Instead, your business logic code should define an interface for every use case (IProductsRepository, ICustomersRepository, etc). Each interface will have only the concrete methods required on each case, no more. Note that if you wanted, you could still implement all interfaces with a single class or with a lot of shared code in a base class in the infrastructure layer, but you can always move one interface to a completely different implementation. Of course, the interface has to abstract the whole data access implementation, not only the ToListAsync part.