How I Use The Generic Repository Pattern In Clean Architecture

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
the repository pattern is one of those really divisive topics in software engineering some people love the repository and some people hate it but I want to talk about how I use the repository pattern and what are the benefits that I'm getting from it and I also want to show you a generic repository implementation that doesn't suck I usually work with domain Centric architectures and one of those is the clean architecture and what I like to do is to Define my repositories in the actual domain layer for example here is the product repository and you'll notice that it's a specific repository this means that I'm going to define specific methods on the product repository that will allow me to manipulate the product entity if I take a look at a few other examples for example the order repository you'll see a similar pattern and in the customer repository you'll notice the same pattern again and the only difference is a very specific method that I need for checking if an email of a customer is unique another way I like to explain it is that these are domain specific repositories because they only operate on my domain entities you'll also notice that I'm using strongly typed IDs whenever I'm referencing the specific entity identifier and this helps me make my domain more expressive and I get compile time safety from using strongly typed IDs so I Define the repository interfaces inside of the domain layer and the implementations sit in the persistence or infrastructure layers depending on if I'm using more granular projects so inside of the repositories folder I will have the customer repository the order repository and so on so these can be very specific in this case I'm using EF core but I could be using any other persistence mechanism and then how I actually use these is in the application layer where I Define my use cases is let's look at a simple use case like creating a product I will inject my repository and the unit of work which allows me to just persist changes and Implement some cross-cutting concerns and then inside of the handle method I just create my entity add it to the repository and call the unit of work to process the changes and this is something that I will repeat in most of my command handlers and the complexity of the logic will vary depending on the use case that I'm implementing another example is in the orders folder let's take a look at the remove line item use case you'll see that I'm using the order repository to First fetch the order from the database before invoking my domain method to execute the remove line item operation and then I process these changes in the database by calling unit of work save changes so at any point in time I need to work with my entities I only load them through the repository and I use the repository or the unit of work to manipulate the benefits that I get from using this approach is that I can Implement my use cases only using abstractions and I can focus on the business logic without having to worry which persistence mechanism I'm using I still couple to EF core to some degree because I'm using EF core features like change tracking and include statements under the hood but this is something that you can deal with if you ever need to switch providers but this isn't my main argument for using a repository because I'm defining the repository interfaces in the domain layer it means that the domain can dictate what someone can do with this entity inside of a use case that's a really powerful way to design your domain because now I'm exposing a contract that forces My Consumer which is the use case to use this contract how I defined it which means that I can dictate what operations are allowed on this entity and what operations aren't also because I'm depending on interfaces inside of my handlers I can easily implement unit tests by just mocking these interfaces and providing the appropriate implementations you can use whatever mocking library that you want like n substitute or Mock and then you can just focus on testing your business logic in this case there is no business logic presence so there's not much really to test but if I take a look at a slightly more involved example like the add line item command Handler it performs some checks to see if the order and the product were correctly returned from the database this is something that I can easily unit test with my interfaces and it's also a valuable kind of test to have so with my approach to the repository pattern where I'm placing the repositories in the domain project I get the stability out of the box I also get a very constrictive design where I can say in my domain layer what are the available operations for my domain entity and I'm also using this specific repository pattern where the interface is only going to contain the methods that I manually Define so I will actually have to stop and think if this is something that I I need with a generic repository that already exposes a bunch of methods some of them will get unused and some of them will get abused because anyone can do whatever they want with a generic repository the downside with my Approach if I take a look at the specific implementations for example take a look at the customer repository here you'll see the get by ID async method and then the add method and if I open up the order repository there's the same implementation of the add method a slightly different get by ID let's take a look at the other two that we have like the product again the add method is the same and so on so there is some repetitive code and duplication in your code is only tolerable up to a specific point I like to use the rule of free when I'm thinking about code duplication which states that if something is duplicated twice then you can probably live with it but if it's duplicated for the third time then it's time to create an abstraction and in this case I'm going to apply the generic repository pattern only in my persistence project to solve the problem of code duplication so here's what I'm going to do I'm going to create a new class inside of the repositories folder which I will call repository this class is going to be internal to the persistence project and it's also going to be abstract I can't create an instance of the repository I can only implement it from my other classes I'm also going to make it generic and the generic argument is going to be the T entity I'll add a generic constraint saying that t entity has to be a class implementing The Entity class from my domain layer so with this design I'm forcing any class implementing the abstract repository to use an entity as the generic argument you're going to see why this is useful in just a moment but let's see what else I'm going to need I'm going to expose a protected read-only instance of my my application database context and let's give it a name of DB context I'm going to create a protected Constructor that the implementing classes can use to pass me the instance of the database context and I'm just going to assign this field from The Constructor and now what I can do is Implement all the repetitive methods that I have in my specific repositories to reduce code duplication in my project let me close down the application layer so that you can see the folder structure that I have and let's add the first method which is going to be the add method it's going to accept an argument of the entity and inside of the implementation I'm going to say DB context and the interesting thing is you can directly say add entity you don't need to specify which database set this entity belongs to EF core will be able to figure it out but if you want a more specific design you can say set of the entity and this will give you the database set for this specific generic argument and then you can call the add method so whichever way you prefer both are going to work with ef core I'm going to add two more methods that are very similar to this one so the first one is going to be the update method and I'm going to call the respective update method on the database set to update this entity and the next one will be remove to remove this entity from the database set this covers the basic methods for adding updating or removing an entity from the repository and let's see how we would use this so I'll head over to the customer repository and I'm going to make it implement the repository Base Class I'll specify the customer entity as the generic argument now you'll see that I'm getting a compiler warning so let's go to the customer class and fix this I'm going to make it implement the entity based class and now if I go back to the customer repository you'll see that we are no longer getting the compiler warning and I just need to pass the database context to the base Constructor I can also get rid of the private read-only field that I have inside of this class and I can replace context with DB context which is a protected class so let me fix these errors now the next thing is the add method is already defined in my base class so you can see we slightly simplified the customer repository let's close the files that we have here so that I'm only left with my repositories and let's fix the product repository in the same manner so I'm going to say repository specify product as my generic argument product will have to implement the entity base class and now I can finish my repository implementation I need to pass this to the base Constructor and I'm going to get rid of of the field that I have inside of this class I'll replace the underscore context field with the DB context which is coming from my base class and you'll see that it's complaining that I'm hiding the base methods for adding updating and removing a product which means that I can get rid of all of these and this is what I'm left with in the product repository now let's go to the order repository and see what I can improve there so I'll implement the repository abstract class specify order as my generic argument which already implements The Entity base class and I can go ahead and pass the database context to the base Constructor I'll get rid of this private field I'll remove the add method because we already have it in the base class and I'm going to fix the Gateway ID async method this is what I'm left with in the order and the customer repositories which only Implement these specific methods for fetching a customer checking for email uniqueness and in the product repository we have the same thing now let's see how I would move the get by ID async method to the generic repository let's for example copy the implementation that I have here in the get by ID async in the product repository and I'm going to paste it inside of my abstract repository class so we have to replace product with the entity then we have to replace the products database set with set of the entity and we run into a problem that the ID property isn't available on my entity based class the second problem is I can't be passing in a product ID here because this is a generic implementation so I'm going to show you how to fix this right away I'll head over to the entity based class and I'm going to give it a generic argument which is going to be the T entity ID I'm also going to define a property in this class that will be of type T entity ID it's going to have a name of ID and I get an init Setter I'm also going to add a protected Constructor for my NAD Base Class accepting a t entity ID instance and it's only going to assign it to the property value now I need to clean up my entities let's start from the customer which has the customer ID as the strongly typed ID and it's only going to pass it to the base Constructor give it the ID value and I no longer need the ID property in my customer class I'm going to quickly fix this for the product entity and the order entity so here I have the product ID I'm going to pass this value to the base Constructor and get rid of the ID property in the product itself the second problem is I'm having some private Constructors here which are required to work with ef core so The Simple Solution is to just Define a parameter-less protected Constructor on the entity-based class and now the Constructor inside of the product will no longer cause a compile error let's also fix the order entity it uses the order ID as it's strongly typed ID and I'm going to get rid of this property and you'll see that everything else is compiling just fine so now I can go back to my repository and you'll see that the ID specified here is showing up because now I have it on my entity based class but the problem is now my generic argument is complaining because there is no longer The Entity type this is now a generic class so I have to expand my generic repository with t entity ID I can now pass this to the NAD Base Class to say this is the type of the strongly typed ID and I will also have to add a generic constraint that the T entity ID is a class even if you are using a record for your strongly typed IDs this would work fine and I need to replace the method argument here from product ID to T entity ID so now the get by ID implementation is generic and if I head back to my customer repository I need to fix it to specify the customer ID which is the second generic argument and I can also remove the get by ID async method because it's already implemented that in my base repository the same applies for the product repository so I need to specify the product ID get rid of the get by ID async implementation and you'll see that all of the methods in the product repository are implemented by our generic repository the only difference being the order repository which is using an include statement here inside of the get by ID async method there are multiple ways how you can fix this I'm going to specify the order ID argument here one approach could be to expose an implementation of this method the get by the async method in the generic repository which accepts an include statement that you can pass and decide what you want to include another option could be to make the get by ID async method virtual inside of the base repository and it's going to have the default implementation in place but if you ever need to change it in a specific scenario like we have in the order repository we can just override this method and provide our own implementation like in this case where I want to use the include statement to also load the line items together with the order a side effect that I introduced because of the changes that I've made to the entity based class making it generic is inside of my application DB context I have a method that's accessing the change tracker to get the entity entries because the entity class is now generic I can no longer write this code and make it compile so I need to come up with a solution for this scenario in this specific case I'm extracting the domain events present on this entity and Publishing them using mediator if I take a look at the methods that I'm using from my entity type it is the get domain events method and the clear domain events method so I can go to my entity based class and I'm going to use a refactoring on this class to extract an interface it's going to be called I entity ID I'll move it to its own file and I only want to have the get domain events and clear domain events method let me create this interface now my entity is going to automatically implement this interface and here is what the interface looks like and what I get to do now is head back to my application DB context and just replace this with I entity the interface exposes the methods that I need to use and everything will work the same as before if you enjoyed this video about the repository pattern you should go ahead and watch this video next thanks for watching and until next time stay awesome
Info
Channel: Milan Jovanović
Views: 26,193
Rating: undefined out of 5
Keywords: generic repository pattern, generic repository pattern c#, generic repository pattern in asp.net core, generic repository pattern c# unit of work, generic repository pattern in asp.net core mvc, generic repository pattern entity framework core, generic repository pattern c# dapper, generic repository pattern in asp.net core web api, repository pattern ef core, generic repository pattern ef core, generic repository c#, repository pattern c#, anti-pattern, entity framework core
Id: kwehxBDX_o8
Channel Id: undefined
Length: 17min 14sec (1034 seconds)
Published: Fri Aug 25 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.