NestJS Abstract Repository Pattern

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so far all of our micro Services we've used haven't actually used the abstract repository pattern so let's consider the scenario that we've built our entire application out and we find that our orm is making sub-optimal queries the problem is that we've already set up all of our code base and all of our endpoints are sort of coupled to the data layer so in this instance the orm we're using is type orm and all the methods that we're using from our services are coupled to the actual business logic itself to you this means that if we want to change any of our methods we have to go to every single endpoint and change the type irm method manually now if we wanted to change our orm let's say we wanted to move from type orm to Prisma or perhaps we wanted to write some raw SQL and have a custom method our system will be coupled and this will be a tedious process now the abstract repository design pattern solves this issue and it decouples all of the business logic from our services from the data layer let's say that we want to add another user to a user table now instead of using type RRM save method directly in the user service as we have been doing so far we can delegate all of the type RM specific data related operations into an abstract Repository so what this does is it decouples the data layer from the business logic in the user related stuff the abstract repository itself is abstract and it can be extended by any entity so in this case the user entity can extend it and then we can have all of the users user business related all the user related business logic in the user service itself and the abstract repository can have the interaction between the data layer separating the concerns this means that if we wish to change our irm or create some custom methods that we want to use we can simply update the abstract repository class and those changes will propagate throughout our entire application where we've consumed those um methods for any developers that have experience in building apis long term or maintain apis you know how important it is particularly on the back end to structure the API properly from the beginning if we don't structure the API properly from the beginning it becomes a nightmare to change later on especially if we have dependencies and coupled logic between various services so separating these concerns is very important and it's important to do that early on in the project the abstract repository pattern is a specific implementation of the facade structural pattern outlined by the gang of four in design patterns in sjs nest.js is object oriented so awareness of you know all of the object-oriented related design patterns is really important and it can help structure your application from the beginning so you can ensure that you have long lasting and quality code base throughout the life cycle of your API put simply structural class patterns are used to use inheritance to compose interfaces or implementations the facade pattern provides a unified interface or a set of interfaces from a subsystem so in this case our abstract repository class it's going to have the set of interfaces for the type orm subsystem the interfaces that we're exposing and we're doing so to decouple the business logic associated with the specific entity class that we're extending from the abstract repository method so just before I get started implementing the abstract repository pattern in sjs I just wanted to give a shout out to one of the subscribers Alejo it was actually his idea to do this and he actually sent a PR through onto GitHub um really well done really well communicated explaining all the benefits of this pattern um you know I've checked out his profile and you know he studied computer science and he has a huge amount of skills here um so he's looking to collaborate with other developers um so if that's of interest you hit him up um and yeah I just want to take this opportunity to say that this is you know an open source project everyone's free to contribute if they wish um if you have any feature ideas or you see any bugs or anything like that you know please feel free to send through a PR and um I think you'll be really exciting to work together and build out features together for this project um so with that said let's jump into the code so basically what we want to do here is we firstly want to set up our abstract repository here and to do that we're going to implement an interface for that so this is the part where it's coupled to the actual orm itself so the interface itself that's just simply the signature and by the way since this was a PR I'm going to show code block by code block of what the code is in a logical order so I'd really like and appreciate your guys comments to see if you prefer this approach or if you prefer me typing it out line by line um I guess it depends on person by person but I've had some comments saying that when I type things out it might be a little bit too slow um and you guys may prefer this approach but if you if this is too fast or anything let me know uh and I can switch back so write a comment to let me guys um let you guys know what your preferences are um and then I can adjust them accordingly um and of course this code's all on GitHub so you can just clone it if you have any issues at all following along um so basically we want to create the abstract Repository and the abstract repository for our case we're using type RM so we want to use all of the type orm methods um and we're going to name our methods that are coupled to the repository um you know and we're going to have an interface for those which we can implement so just to illustrate that we've come to the in the folder structure here if we go into our shared folder and we open up the source folder we have these repositories folder here so we can create a repositories folder and we can have our base stuff in here and this is where we're going to have both the repository and the interface which correspond with one another for the base um so in the base interface we can start to we firstly what we want to do is we want to use typescript generics because the idea is we can pass in whatever entity we need here um so for example we'll see the user entity being passed in here and then that repository will have access to all the data layer um based on the particular service or area that we're in so this will come clear as we work on this um but here we see like let's say we want to create something so we want to create a new resource so T is the generic and that can represent the use entity for example um now we've got this deep partial type here because we might not necessarily need to um you know include all of the properties so for example we might not need to include the ID when we're creating a resource so we can have this deep partial where it's um you know it it basically sets the properties to being optional so that way we can have the entity defined but then we can also you know pass in or not pass in the optional properties and the idea of this is we're decoupling the user stuff from the actual data layer itself so we can see that when we create our repository based on this interface that um you know would only need a change in the one spot if we wanted to change our orm or um you know perhaps create raw SQL things where those methods aren't used throughout our entire application so you know let's just create some of these signatures here so we want to create a resource we can take whatever the data type is and return it you know we want to create many so this is exactly or a similar sort of thing but we're just returning and taking an array of resources we have a save method here which is similar to the create method we have a save menu which is sort of like the create menu obviously the subtle differences um which you can learn about in the type orm documentation um but again we're not necessarily going to be tied to type home yes we are using type one but let's say we want to move to Prisma or something else we can still call these methods from for example we use a service or one of our other service micro service Services um and then we can just change the coupling in the actual repository itself so it's super handy super clever um pattern here find one by ID um you know you take an ID our IDs happen to be numbers here but if you're using or something like that it might be a string um and that just basically finds it by the ID and gives you back the resource fine by condition this is a similar sort of thing um but you know you can have your wear clouds into sort of filter things and there's this find one options type here which helps you to do that find or similar to find and you can also pass in options and they'll find multiple records or rows remove that's just um deleting something um fine with relations this is something you might need if you need to do any joins or anything like that where you can actually just pass in the relations which again this is coming from the type orm um find many options uh type here um and pre-load so you can just preload the particular entities um so this is essentially the interface so this is just like the signature for all the methods that we're going to use and the actual you know connection let's say to the orm or the actual coupling to the irm is done in the space repository here so we see that with exporting an abstract class and we've called it the base abstract Repository and basically we want to use or extend this has ID um property here which is just having the ID on each of the entities and the reason for that is because we might not necessarily have ID on the actual type itself um because you know when you save a record for example you don't need to have you don't need to pass in the ID um but the actual entity itself or the actual you know in the data layer in the actual database itself it will have a corresponding ID um so we just go ahead and extend that so we can have this generic type here which just extends that um just to make sure that we have that property on it some of them will already be defined like that um but this just takes care of the cases that don't have that explicitly written um and then what we want to do is we want to implement the base interface repository so we just created that we created this generic type here and then we can pass in our particular repository or entity into that here and then we can see that the base abstract repository is going to have all of those um that are on the entity itself and then plus the ID if it's not already included and then we're implementing all of these base methods That Type O arm give you so for example oh well firstly we need to use the Constructor to inject the particular entity that we want which we normally do this in the service or we have been doing in the service but again we're decoupling it the data layer from the service itself so that's why we have the repository here and we're implementing a generic interface for the particular entity that we're using um and then we're just you know setting this entity to The Entity such that we can use it from within this uh abstract Repository um or this abstract repository methods will be used in a super uh well a subclass let's say um so we can see already that the signature here matches the signature here um so here we've got the save method here um and we can see that it's also taking the deep partial and return the same type um so that's all well and good um and then this is where we're actually doing the type orm specific stuff so in type RM there's a save method for example so when we you know make use of this and extend this abstract Repository wherever we extend it it will refer back to this one spot and that's really really nice in case we do change the alarm and then we're not coupled in all of our places and let's say we have a big application and it probably would be in a micro Services architecture um you have multiple people multiple teams working on multiple services if you were to make a change let's say your SQL queries are sub-optimal and you just find a way better oh I am or even if it's just like you're writing raw SQL yourself based on the same method you can just make the changes in this one spot so this is sort of a problem that you solve after a couple of years of development or something you become aware of you know if you build a system and then you need to make these changes because it is a real thing where if your need to make like a search query for example you have millions of rows of data um you've got some complicated joins you know you might have a 5-10 second query which is obviously not acceptable um because the irm while it's really good at some of the simple um cases um and it's getting a lot better at some of the more complex cases there are still some really uh complicated queries that you might need to override with raw SQL and if you've already set up your system and you might not be fully aware of those until a later Point uh this is where the decoupling becomes really important so it's a really really great pattern really really clever um and then of course we can implement the other patterns we've got the save many um this is basically the same uh underlying method here um we got the create which is just calling the typo RMS crate here create many um fine by one oh sorry find one by ID which essentially it takes the ID which is a particular type of option um and you know you can find that particular Row for the entity based on that ID find by condition a similar sort of thing but you're finding based on the filter condition find with relations so you can just put in that as well so these are all based on the find one options this is coming from the type orm so again the abstract repository this is this is tied to the um particular RM implementation so we're using type 1 but you can see that like let's say we switch to Prisma I don't know what prisma's methods are chord but let's say um you know find one buy was just find one you could just come here and change that to find one and use that or if you wanted to write your own SQL query you could use the query Builder and write some raw SQL but as you can see you can make the change in the one spot and then they'll propagate throughout all the places that it's used throughout the application um so fine with relations find all it's just in the fine method here to return the array here uh remove it's just delete the resource um preload preload the um entity I'll empty like um and it's important to note that you can have you can obviously extend this you can have more um of the orm specific stuff in here if you need that but these are the main sort of crate operations so I'll have these in the um base repository here um so now that we've set up the base Repository let's actually have a particular implementation of that and as we alluded to earlier we'll do that for the users repository to start off with so let's once again let's start with the user repository interface now the user repository itself um it's going to have all of the methods that come from the abstract repository and we do that just by extending the abstract repository for that particular entity so we're doing it for the use entity so recall the user entity has ID first name last name email password um so based on that entity we can see that if we come to our abstract repository method um you know it's simply going to put the T in for the particular entity um and obviously the benefit is we can use any entity on this an entity or the repository here won't be coupled to the orm because we're using it through this abstract class here um so we don't actually need any other methods because the abstract method um methods that we have here um this is going to cover pretty much everything we need but it's notice notice notable to note that let's say we had some very user-specific stuff here we could easily just create a class in here um and we could ex you know sorry a function in here in the interface and we could do a similar sort of thing in the corresponding Repository so in the repository itself we just have this class we call it users Repository this is going to extend the base abstract repository with the user entity type and it's going to implement this interface here so this interface here it extends the base interface and optionally or perhaps not in this particular case but in other uh implementations of the repository method that we we may actually have some extra functionality there depending on our business rules and use case so let's just go ahead and use the Constructor here and we can inject the repository so we inject the user entity um and you know we can just pass in the repository as the generic type here and then since we're you know doing this extension here um we can actually just call the super method in the Constructor so they'll call the Constructor in the abstract Repository um and that will allow us to sort of make use of whatever particular entity we are so the instance of The Entity such that we can call all of those um orm methods there um so we do the same sort of thing for the shared service so we create the shared service before um and one thing to note is I've actually moved the user's Repository the user entity and this uses interface here this is all in the shared because the reason for that is because it's not just the auth module that will need the user related stuff it's actually um pretty much all of the micro Services they may need information relating to the user um so just in case you're wondering why those files got changed there but essentially what we're doing now is we're looking we want to do a similar sort of thing for this shared service here so we see that we've got this acknowledge method here get rmq options here so we can just come here in the interfaces folder here and we can just pretty much copy the signature here such that we have our interface that we can Implement there so just go ahead and do that there um and then what we need to do now that we've got the base repo set up and also the user repo alongside with this shared stuff here um we can inject that into our auth module we can use a provider and we do this in a slightly different way it's a little more sophisticated than it was before but essentially we can have the provider here um and then as a provider we can use this provide and we can label it something so in this case we're going to call it the auth service interface here and we can go ahead and use the auth service class here we do a similar sort of thing for the user Repository so the user's repository interface here and actually I think we may need to come back to this where this came from but the user's repository interface we created and the user's repository is the particular class that we're using and then likewise with the shared service um we have the shared service interface that we're providing based on the shared service class so let's just jump over to the auth controller we'll come back to this auth service interface um so basically the auth controller it's very very similar to how it was before we just made one alteration to how we're injecting the in the Constructor and we see that we're injecting it based on the name so that corresponds with what we've called it here so we've provided it this here um and the particular class that we're using is the auth service itself so that pretty much stays the same as it was we do the same thing for the shared um so let's just make sure that our interface um well firstly we have an interface um and then our the contract of the interface uh matches the uh implementation from the actual class or service that we're using there and then we can just use the methods um the same way as before um so in the auth service here in the Constructor here we're doing a similar sort of thing where in the service itself we need the user's repository stuff so we just go ahead and we inject that based on the interface here and that user repository is of the user repository interface type so that's handy too and then we've got the auth service interface itself so this is Something That We're this is the final piece here um so we have it the interface the auth service interface and this is just going to if we look at the methods here we've got get users find by email etc etc this is where we're just writing the signature out for all of those such that you know the contract for the interface matches the service um because we're implementing the interface for that class so we can just go ahead and add all those in you can pretty much just copy the um method just like that and copy it in so I'm just going to go ahead and paste these in so you've got find by email hash password register that's password match validate user log in and verify JWT uh and the final thing that we've done here is all I've done here is we've created a bunch of extra files in here I've just gone ahead and I've just organized these as Imports here oh sorry exports here um and what that allows us to do so you can just copy these in um but what that allows us to do is for example in uh our files here like the auth controller we see that we've got this shared service from App shared uh and that just saves us from having to do the um you know all those Imports where it's slash shared service and you know if there's nested folders since we now have nested folders um you know we want to avoid having really long fall pass and then also we can if we need to we can import multiple files from the same area so let's just check that everything is working so let's go ahead and open Postman here so let's just try and make a API call to auth we get our users back um presence we get the 403 let's try logging in we get our Json web token so the auth service definitely works um API Gateway is obviously working and then finally let's just check the presence token by putting the JWT to make sure that microservice is still working and then we get our resource here so in a nutshell that's the abstract um repository uh pattern implemented in nest.js it's really really quite a powerful pattern when you think about it and it's really clean it it structures your code a lot better and decouples the data layer from your business layer um so if you found that useful please subscribe to my YouTube channel in the next video will start to look at a little bit of setting up authentication in react native and how react native Works um and of course if you you know want to contribute uh in any way I'll be super happy if you want to collaborate um and Keen to see any PRS or you know just get in contact with me um if there's anything in particular and thanks so much for watching cheers I'll see you in the next video
Info
Channel: Jon Peppinck
Views: 8,044
Rating: undefined out of 5
Keywords: nestjs, typescript, nestjs tutorial, typeorm, nest, javascript, dependency injection, nest js, express, angular, repository, node.js, repository pattern, nestjs abstract repository pattern, nest repository pattern, typescript repository pattern, repository pattern java, repository pattern kotlin, repository pattern architecture, repository design pattern typescript, design pattern, design patterns, patron repository nest, factory design pattern, patterns
Id: QVOknvmfV-Y
Channel Id: undefined
Length: 29min 40sec (1780 seconds)
Published: Mon Jan 09 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.