CRUD REST API With Clean Architecture & DDD In .NET 7

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in today's video we'll create a set of crud endpoints on a single entity inside of a project that is using the clean architecture the cqrs pattern with mediator and domain driven design I'll discuss what are some of the best practices that you should be thinking of when implementing your features and I'm hoping to make this a more beginner friendly video so let's dive in I'm going to use the product entity for the set of endpoints which are going to be the create read update and delete endpoints if you take a look at the product entity itself you'll notice a few things I'm using a strongly typed product ID which is wrapping just a good I'm using a money and skew value object so if we take a look at those the product ID is just a record wrapping a good value the money value object is also a record wrapping the currency and amount properties and the stock keeping unit or SKU is a value object with a static Factory method that enforces some logic during the creation of this value object now the only constraint is that it has to be eight characters long and the value is a string The Next Step would be how do we configure this with our object relational mapper which in this case is Entity framework core inside of the persistence project I have the product configuration which applies some fluent configuration to our entity we Define that the ID property as the primary key we also need to define a conversion to and from a good value because we are using a strongly typed ID the same applies for our value object so for this q and the price we apply some conversions for the skew we are using a simple value conversion to fetch the string value and for the price because it's a complex value object with two properties we are using owned entities which is going to translate the current C and the amount properties into individual columns in the database let's start by defining the product repository interface inside of our domain project so this is going to be the i product repository and I'm going to add a single method inside which is going to match the endpoint that I'm going to create first so this is going to be a void method that I'm going to call it add and it's going to accept just a product entity we are taking the bottom up approach and starting with our repository interface with a single method the next step is going to be going one layer up which is our application project and defining a feature folder for the products so let's start with that and now I'm going to add another feature folder for our first feature which is going to be the product create feature because we are using the secure as pattern I'm going to start by introducing a create product command so let me Define that I'm going to be using a record instead of a class and I'm going to define the properties that are required for the create product command I'm going to accept the same properties that I have on the product entity which are the name and then I have a string for a skew and a string for the currency and a decimal value for the amount now I'm going to align these vertically so that it's easier to see and to make this a command we need to inherit from irequest which is coming from mediator now because commands can only alter State and do not return any values I'm going to implement the I request interface which returns no value now that we have our Command we need a respective command Handler so I'm going to add the create product command Handler now inside of this class we need to implement the I request Handler interface and we specify the create product command which we want to handle and let's implement the handle method because we already added the iproduct repository let's go ahead and inject it inside of our class so this is going to be the iproduct Repository and we can inject it from The Constructor and the next step would be to convert our create product command into a product entity instance which we are going to add to the repository let's start by creating a new product instance by calling the parameter list Constructor that is available on the product class and let's say I want to set the ID property to a new product ID and I'm going to give it a new guid value now you're going to see that this is going to complain that the property product ID has a private Setter and this is a common practice with domain driven design you don't want to allow your properties to be modified outside of the product class so how do you solve this well you can define a Constructor so let's create a Constructor in the product class that is going to accept the values that we need so I'm going to auto complete this and I'm going to use this Constructor back in our create product command Handler let's update this slightly instead of passing the product ID directly I'm going to get rid of all of this and we're going to call the Constructor so the first one is going to be the product ID then we need to pass the product name which is going to come from our Command then we need to pass a money object which is going to be a new money instance and we're going to pass it a currency and the amount from our Command and lastly we're going to call skew create and we're going to pass a new SKU instance now I'm not going to bother with validation at this point and I'm just going to pass a now forgiving operator here but you should definitely check that the create method does not return a null value now after creating our product we're going to call the product repository add method and pass in our product now the only thing that's missing is how do we actually save this in the database this is where the concept of a unit of work comes in and you'll commonly see this in projects that are applying domain driven design so I'm going to inject the unit of work interface which is implemented by our database context so when I inject the unit of work what I'm actually getting at runtime is the instance of the EF core DB context then I'm going to call a weight now I'm going to have to make this an asynchronous method to be able to do that and I can say unit of work save changes async and I can also pass it the cancellation token now that we have our Command Handler we need to somehow expose this on our API so let's head over to the API project and inside of the endpoints we're going to create a new class that is going to hold the product endpoints now here I'm using the Carter library to group my minimal API endpoints and we're going to use the endpoint route Builder to Define our endpoints because we are building a restful API the endpoint that is responsible for creating a new resource should be a post endpoint so we're going to call map post and we need to Define our route now by convention you want to specify the resource name in plural so this is going to be products and then we need to specify a delegate for our endpoint body so two things that we need here one thing is going to be the body of our request which is going to contain the name skew currency and amount values now you could create a separate class that is going to represent your request or for simplicity's sake you can even use the command directly so let's for example use the create product command now the second thing that we need is the I sender interface from mediator which we are going to use to send our Command and now the body of our minimal API endpoint becomes sending the command so I'm going to make this asynchronous and I need the async keyword here so I'm going to say await sender send and we're going to send our Command we want to return let's say 200 okay result so I'm going to return results and create a new OK result so this is our post endpoint for create getting the product let's follow the same approach with the other endpoints that we want to expose so we go back to the domain layer and inside of our product repository let's define the method that we're going to use let's say for the remove endpoint so I'm going to add a remove method which is going to accept a product instance and the next step is creating our Command and command handlers so in the application layer we create a new feature folder we can call it remove or the command is going to be delete because we're doing crud so let's say this is a delete product command so we need the respective class so this is going to be the delete product command and the only thing that we need here is to make it a record and define the properties that are required for our commit now the one that we need is the product ID we can choose between passing a good value or a strongly typed product D let's go for the strongly typed ID route and I'm going to Define our product ID property and this is again going to be an I request returning no value the next thing we need is our Command Handler so this is going to be the delete product command Handler we're going to make this glass sealed and we're going to implement the I request Handler interface and specify the delete product command of course we're going to start by injecting the i product Repository and we're going to also inject the i unit to work to be able to persist these changes in the database so let's generate the Constructor and let's approach implementing the handle method now the first thing that we're going to need is to somehow fetch the product from the database how do we do that well let's go back to the product repository and we're going to need another method here so let's say this is an asynchronous method returning a product or it could return null if there is no product and let's say we want to call it get by ID and it accepts a product ID instance so this is the method we're going to use if I go back to the delete product command Handler I can say await repository and let's actually rename this to product repository and we can call the get by ID method now because this is an asynchronous method you want to rename it to have an async suffix so this is going to be get by ID async and you can pass it the product ID because the get by ID method returns a product that could be null we need to check if the product is not and in that case we have a few choices the simplest solution would be to throw an exception in a project like this you have a few places where you can place your custom exceptions I prefer to place them in the domain layer inside of the feature folder so let's create a product not found exception so product not found exception and let's make it public and sealed and we're going to implement the exception base class now let's create a Constructor that for example takes in our product ID value and we're going to pass a message to our Base Class that's going to say the product with the ID let's say equal to ID value was not found awesome so let's say this is our custom exception and let's throw it inside of the delete product command Handler so we're going to throw a new product not found exception and we're going to pass it the product ID coming from our delete product command if we pass this check we are sure that the product is not null and we can say product repository remove this product and then to persist this in the database you're going to call unit of work save changes async so this would be your flow for deleting a product you first fetch from the repository apply a null check you remove the product and you persist the changes now we need to expose an endpoint inside of our endpoints class so the convention for deleting a product is using the HTTP delete verb so we're going to call map delete we're going to Define our products route and we want to use our route variable that is going to represent our product ID now what you can do in asp.net core is you can add a guard that checks if this is a good value and if it is then your product endpoint is going to trigger let's define the delegate which is going to accept a good value which is going to be our ID on the product that we want to remove and again I'm going to create the sender service with mediator to be able to publish my command you'll notice that intellisense is working properly in visual studio and highlighting this as a route variable so now I need to create my command or I can do something like this I can say a weight and of course to be able to await I need to make this asynchronous so I wait sender dot send and I'm going to create a new delete product command and I'm going to pass it a new product ID to use the ID value coming from the route a convention I like to use for my delete endpoints is to return a no content result so I'm going to create results no content if this is successful what happens if we run into a product not found exception so let's wrap this in a dry catch block and we want to catch the product not found exception so we want to move this into the try block so this is our success path and in the catch Clause we want to handle the case when we encounter a product not found exception so in this case we want to return let's say results not found and we can pass in the exception message to tell the consumer of our API that the product was not found so this would be our delete endpoint let's move on to the next one which is going to be the update endpoint now you could be tempted to go ahead and Define an update method in your repository this is generally not required by EF core because it's going to track changes to your entity automatically but if you wanted to you can go ahead and Define an update method here although it's not going to make a lot of sense in the context of using EF core but for the sake of completeness let's define an update method now we need our Command and respective command Handler in the application layer so this is going to be the update feature now we want to add the update product command so I'm going to define the update product command and let's turn it into a record and what do we want to be updating on the product and what are some of the additional properties that we're going to need so first of all we're going to need the product ID to know which product we are updating then I want to pass in the name of the product so this is going to be the new name the updated name we can also accept this queue and let's accept the currency and the amount for our price so this is pretty much the same as in the create product command but we want to define a different object because it represents a different feature now with the command in place let's add our Command Handler so this is going to be the update product command Handler and let's implement the I request Handler interface so we Implement I request Handler we specify the update product command and let's add the handle method we are going to inject our repository so I product repository let's give it the proper name and also the i unit of work so with this in place let's generate the Constructor and we want to approach our handle method so I'm going to make it asynchronous and the first order of business is going to be fetching the product that we are updating so I'm going to say VAR product is going to come from our product repository get by ID method so we got a product let's apply another check so if the product is null we are going to throw a new product not found exception and we're going to pass it the product ID otherwise we want to update the product name to the one on the command and also let's say the product SKU to the one and the command of course we have to call you create and pass in the value from our Command and also give this a non-forgiving operator because this can return null now now you're noticing that this is complaining again because we are trying to set a property that has a private Setter with domain driven design you are not allowed to directly set the property values because they should be private so how do you go around this the correct solution would be to expose methods for updating your product let's create a new method that is going to be responsible for updating the values on our product so you can call it update change information edit information whatever you like I'm going to use just updates to keep it simple for the arguments we want to accept a name a money object which is going to be our price and ask you object and the implementation is very straightforward we just set the values directly on our entity so with this in place I can go back to my update product command Handler and replace all of this with a call to product update and we can pass in the name we can create a new money object and pass it to currency and amount and I'm going to create a new SKU and pass it as the last argument so this is going to be skew create and we're going to pass it the skill value so now we can call our product repository update and update this product although I mentioned because you're using EF core this is going to be already applied behind the scenes so it's kind of unnecessary and the last step would be calling unit of work save changes async and passing it the cancellation token the benefit of keeping this logic inside of the product class is encapsulation and if you also have some side effects that you don't want to expose directly keeping them encapsulated inside of a method is a really nice approach we also need an endpoint for our update product command so let's go to our products endpoints and between the post and delete endpoint I'm going to add another one now for updating the convention is to use a put endpoint so I'm going to create a put endpoint and I'm going to use the same route as I did in the delete endpoint so the question is now what should we be accepting in our Delegate for the endpoint if I accept the update product command directly the question is how are we going to populate the product ID and if I leave out the product ID from the route then this doesn't really follow restful conventions so there are a few approaches how you can go about this you can make this nullable and set it after the fact or you can use a separate object to Define your request body so this is the approach I'm going to show you so let's say instead of the update product command you define a request object so this is going to be the update product request and it's going to lack the product ID so you're going to take this in your body so this is going to be the ID coming from the route then we're going to have the update product request coming from our request body and also I sender coming from mediator so let's add the endpoint curly braces now it's probably a good idea to add a from body attribute here to make it very clear where this is coming from so the update product request is coming from the body and the ID here is coming from the route now we need to create our Command instance so this is going to be the update product command and let's create a new product ID which is going to use the one from out and then we just pass the values from the update product request to the update product command so we need to pass in the currency and the amount so now if I make this asynchronous I can go ahead and await the sender send command and we need to just return something from our endpoint you can either return 200 okay or 204 no content I prefer returning 204 no content so this is the approach that I'm going to take so with this in place we have three out of the four endpoints that we want to Define and the last endpoint that we want to make is the get endpoint for fetching a product by the ID now I don't want this to touch our domain project because querying is not a domain concern so I'm going to directly create a query I'm going to add a get feature folder and inside of it I'm going to create get product query this time what we had so far were commands commands can change State and they should not be returning any values queries on the other hand do not change state but they return data to the caller so this is the get product query and we want to accept a product ID so we know which product we are returning now this one has to implement I request and the generic variant and specify what is the object that we're going to return I'm going to create an object that I'm going to return from this query this is going to be a record and you can call it in a few different ways a common one you'll see is something dto so this is going to be a product dto which stands for data transfer object my preference is using request and response so if something is coming into my API I suffix it with request and if I'm returning something from my API I add response so I'm going to use the product response because I'm exposing this from my API I want to use primitive values only so I'm going to return the ID as a good and let's add the other property so we have name we also have a string for a skew and we have the string for the currency and the decimal value for the amount so I'm going to return a flat structure and I'm going to use this product response as the generic argument for our get product query now I need a respective query Handler so I'm going to add a get product query Handler and let's make this sealed I missed the name so let me fix this so get product query Handler awesome I need to implement the I request Handler interface and I need to specify the get product query and what is the response that I will be returning which is the product response so we add the handle method and now the question is what is the most optimal way to implement a query you don't want to be going the route of using repositories because they are not as performant and a better approach would be to Simply use the database context directly so I'm going to take that approach and I'm going to inject the I application DB context so let's give it a name of context and inject it from The Constructor in the handle method we want to start by making it asynchronous and we want to create our product that we're going to return so let's await let's say context we want to select the products DB set and then I need to apply aware statement that the product ID that we're trying to query is equal to the one that is specified in our Command and if that is true let's select and project our product into the product response I'm going to create a new product response and we need to pass the appropriate values so we're going to select our product ID we're going to take the value then we have our name then we have this Q value and we also have our price currency and we have our price amount so with our projection in place we can say either first or default or single or default now the more correct approach would be single or default because there can't be two products with the same ID but because we are using a SQL database and this is a primary key in any case we can let the database handle that and let's just use first or default async because it's more performant than single or default and we can return our product the product response could be null so so it's smart to do another null check here and let's say we want to throw the product not found exception and we pass the product ID how we handle a query is different to how we handle a command with a command we want to be applying domain driven design principles and we take a more verbose approach with repositories and methods on our entities with a query we're only interested in fetching data from the database and we want the simplest possible approach so we can use our EF core database context directly or you could create some service that is going to return a product response if you don't want to be using EF core in your application layer with this in place let's add the endpoint in our products endpoint class and for this we're going to create a get endpoint I'm going to use the same route as here because I want to be fetching a product by the ID and I'm going to also need the eye center from mediator to be able to send my query and the implementation is going to be straightforward I'm going to write a one-liner to return the product directly so I'm going to say return results okay you're going to await sending of the get product query and we're going to pass it the ID value I of course need to make this asynchronous to be able to await inside and we can also handle the case of the exception so I'm going to add a try catch block and we're going to be catching the product not found exception so we can move this in to the try statement if there is a product this is going to return a product response otherwise let's return a not found response the same as we did in the delete endpoint and we are going to use this here so we're either going to return a 200 okay and the product response or we're going to return 404 not found if we get a product not found exception so these are your create read update and delete endpoints implemented inside of a project with clean architecture using cqrs to logically separate reads from rights with the mediator library and for our domain we are applying domain driven design if you want to know how to set up the clean architecture from scratch I suggest that you watch this video next also make sure to smash the like And subscribe button and until next time stay awesome
Info
Channel: Milan Jovanović
Views: 18,748
Rating: undefined out of 5
Keywords: rest api, rest api explained, rest api tutorial, rest api tutorial for beginners, rest api c#, rest api design, rest api .net, rest api .net core, rest api .net 6, rest api .net 7, rest api clean architecture, rest api ddd, rest api sql, rest api sql server, rest api postgresql, rest api endpoints, rest api minimal apis, rest api asp.net, rest api asp.net core, rest api asp.net 6, rest api asp.net 7, rest api best practices, rest api fundamentals, rest api guide, rest
Id: nE2MjN54few
Channel Id: undefined
Length: 29min 51sec (1791 seconds)
Published: Tue May 23 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.