The main points:
- Strong separation of Concerns
- Independent of frameworks
- Independent of UI
- Independent of database
- Testable
Aim to produce the systems that are highly independent of frameworks, User Interface, databases and testable, so you should be able to model the core of your application and test it without any UI, Database or any Frameworks. The way Clean Architecture is differed from Traditional Layered Architecture is the direction of the dependencies.
Traditional "N-Layer" architecture applications
In traditional layered Architecture we have Presentation Layer, which is depended on Business Logic Layer (BLL), which is dependent on Data Access Layer(DAL)
By applying Dependency Inversion Principle of Object Orientation, between BLL and DAL will end up with Architecture like bellow figure.
Clean architecture
So instead of BLL depend on DAL, DAL depend on BLL. Now the BLL has no dependencies to Presentation or DAL. It completely isolated from any presentation or persistence frameworks. In this Layer we can implement all use cases and business rules and test them no matter what presentation or persistence framework we going to use. And more importantly we can swap any framework in the future for Presentation or Persistence, but Core of the application is not dependent on these frameworks.
Clean architecture (Onion view)
Dependency Inversion Principe (DIP)
There are two parts on the definition.
A:
"High-level modules should not depend on low-level modules. Both should depend on abstractions".
In above example controller is high level module and UnitOfWork is low level module. According to above figure controller dependent or tightly couple to UnitOfWork, which means we are violating the Dependency Inversion Principle. Issue here is if we change UnitOfWork the controller may have to be change, or at least compile assemble in which defined and need to be redeploy. DIP help us to remove this coupling, so instead of depend on UnitOfWork it will dependent on abstraction/Interface that will be implemented by UnitOfWork, if we change this implementation controller will not be effected as long as contracted stays the same.
B:
"Abstractions should not depend on details. Details should depend on abstractions".
Complete()
Product: ProductRepository()
In above example our Interface/abstraction is dependent on ProductRepository which is details or concrete implementation. Which is tightly couple to dbcontext and Entity Framework(EF). So, this Interface is leaky and it’s not good abstraction which means controller is depended on IUnitOfWork even though this is an interface the controller still will be tightly couple to EF. DIP help us to remove this coupling by replacing this repository with an abstraction like IProductRepository. That knows absolutely
Complete()
Product: IProductRepository()
The Repository Pattern
"Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects". (PoEAA – Martin Fowler)
- Decouple business code from data access code
If you use Entity framework and in future if you want to switch to different ORM, you can do so with minimal impact on the rest of application. - Provides better separation of concerns
Controller no longer responsible for querying, querying is data access concern, it’s not the responsibility of a controller, controller is responsible for application flow, so controller receive requests and generate responses. - Minimize duplicate query logic
If we have lots of controllers all using same repository, we can re-use this without duplicate same query logic inside all controllers - Testability
Much easier to create a Mock of repository than is to create a Mock of dbcontext. dbcontext class is big. It carries lots of methods with it and it’s kind of tricky to test if our code using dbcontext inside API controllers
Q. If you use Entity framework and it already decoupled, and Repository pattern is already implemented in EF, So why do we need to do this again?
Reason for this if we have lots of controllers all using same repository, we have to duplicate query logic inside all controllers.
Reason for this if we have lots of controllers all using same repository, we have to duplicate query logic inside all controllers.
Some Consequences adding Repository Pattern
- Increase level of abstraction
Can be a good thing or bad thing. We don’t want too many levels abstractions, but in this case, we are abstracting away from database with EF, abstracting away from EF with the repository, repository sit between dbcontext and controller, so in this case fine. - Increased maintainability, flexibility, testability
All good things. It easier to maintain our repositories that is to maintain controllers with dbcontect injected into the controllers - More classes/Interfaces but less duplicate code
- Business logic move further away from the data
This is good thing. We don’t want to our business logic tight too closely to our data, as then it makes too difficult to move away from particular database technology or ORM technology - Harder to optimize certain operations against the data source
If we want specific queries like return only few fields, RP harder to optimize these queries, what RP do is get entity as whole then shape it after we got data back inside memory. But we can use another pattern on top of this call “Specification Pattern” to achieve this.
inject our repository into the controller, then the controller is going to see what's available inside the repository to use. All of a sudden, controller becomes a lot easier to manage. It's more lightweight and the code is cleaner. Repository has access to the dbcontext and that's responsible for the _context .Products list method. And then Context is going to translate this query into a database query and say “select * from products” and that's going to get passed back up the chain to the controller which would then return those results to the client that was requesting it.
Generic Repository
- When our application growing and have more entities, it is pointless to create repositories for each and every entity,
- The Idea behind the “Generic Repository” is that we just have single Repository that can be use with any number of Entities and we don’t want to create any more repositories. We can just use the one Generic Repositories for all Entities.
- Generic Repository can sometimes have bad reputation, and that because how developers used in the past to get around the problem that a generic repository is just that it's generic and not all of your data access requests are going to be the same, some entities you want to bring back paginated responses, sometimes you want to filter them or sort them in a certain way. When we use a generic repository, this makes it difficult to achieve. So, the way to get around this is to use the specification pattern. So, it kind of comes hand in hand when we use a generic repository and we'll be taking a look at that as well.
Generics:
- Been around since C# 2.0 (2002)
- Help avoid duplicate code
- Type safety
- We mostly use generics rather than create them
Specification Pattern
Specification Pattern can be anti-pattern if you use it incorrectly.
Specification pattern is clear and flexible, It gives us many ways to query our data and return from our database.
- Describe a query in an object
So instead of just passing an expression to a method for example we could define a specification. - Object which contains the query we want to send to that particular method, and it returns an IQueryable<T> which is really an expression tree that we’re going to use to send to our method. That’s going to execute the query on our database.
- So, our generic method would take the specification as a parameter instead of a generic expression as a parameter and our specification can then have a meaningful name.
Unit of Work Pattern
Maintains a list of objects affected by a business transaction and coordinates the writing out of changes. (PoEAA – Martin Fowler)
If we have multiple repositories is the potential to end up with partial updates
Partial update: each repository owns version of the dbcontext which is used. So that we can not just query our data from the database but also add updates and remove entities from the database as well. If each repository has its own version of the dbcontext then one thing could succeed when we save the changes, and one thing could fail in a different repository, that would leave us ending up partial update
Responsibilities of UoW
- Creates dbcontext instance
- Creates Repositories as needed by sharing same dbcontext
- Inside UoW we can create transactions, until we call Complete() method in UoW changes not apply to the database. When we call Complete() method it will save all into the database or it will save non of the changes into the database(roll back to where we were before).
![]() |
| Without UoW - each repository owns version of the dbcontex |
![]() |
| Unit of Work |













No comments:
Post a Comment