CQRS and MediatR In ASP.NET Core

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this video we are going to provide a brief introduction to the cqrs pattern and how the net library mediator help us build software with this architecture if you prefer reading about it and also if you want to download the source code feel free to visit the article on the codemask blog site the link is in the description below the mediator library was built to facilitate two primary software architecture patterns cqrs and the mediator pattern while similar let's spend a moment understanding the principles behind each pattern to explain cqrs let's use the diagram from our article cqrs stands for command query responsibility segregation as the acronym suggests it's all about splitting the responsibility of commands and queries into different models if we think about the commonly used create read update delete pattern usually we have the user interface interacting with the data store responsible for all four operations cqrs would instead have a split these operations into two models one for the queries read operations and another for the commands create update and delete operations the cqrs button makes no formal requirements of how this separation occurs it could be as simple as a separate class in the same application all the way up to separate physical applications on different servers the decision would be based on factors such as scaling requirements and infrastructure so we won't go into the decision path today the key point being is that to create a cqrs system we just need to split the reads from the rights so what problems do we solve with it well when we design a system we start with data storage we perform database normalization add primary and foreign keys add indexes and generally ensure that right system is optimized this is a common setup for relational database such as sql server or mysql other times we think about the read use cases first then try and add that into our database worrying less about duplication or other relational db concerns well neither approach is wrong but the issue is that it's constant balancing act between reads and rights and eventually one side will win out all further development means both sides needs to be analyzed and often one is compromised cqrs allows us to break free from these considerations and give each system the equal design and consideration it deserves without worrying about the impact of the other system this has tremendous benefits on both performance and agility especially if separate teams are working on these systems after cqrs let's talk a bit about the mediator pattern to explain it we will again use the diagram from our article the mediator pattern is simply defining an object that encapsulates how objects interact with each other instead of having two or more objects take a direct dependency on each other they instead interact with the mediator who is in charge of sending those interactions to other party we can see that some service sends a message to the mediator and the mediator then invokes multiple services to handle the message there is no direct dependency between any of blue components the reason the mediator pattern is useful is the same reason a pattern like inversion of control is useful it enables loose coupling as the dependency graph is minimized and therefore code is simpler and easier to test in other words the fewer considerations component has the easier is to develop and evolve now that we've been over some theory let's talk about how the mediator package makes all these things possible first off let's open visual studio and create a new asp.net core web api project we are going to name it cqrs mediator example and let's uncheck the open api support and click create after the project creation we have to install a couple of packages first let's open the package manager console and install the mediator package next let's install package that wires up mediator with the asp.net di container to do that let's use the install package command and call mediator extensions microsoft dependency injection now right after the installation we have to configure our mediator package in the program class so let's navigate there and add builder dot services dot add mediator method of course for this we have to add the missing using directive then we can call typeoff and provide program as a parameter now mediator is configured and ready to go before we move to the controller creation we're going to modify the launch settings.json file let's modify the application url property to 5001 for https and 5000 for http now is the time to add our controller let's navigate to the controllers folder choose add controller let's select api empty controller and add a name products controller with our controller in place let's first just modify the route attribute from a generic controller parameter to products now to be able to use mediator in our controller we have to add a private read-only imediator mediator field the i mediator interface allows us to send messages to mediator which then dispatches them to the relevant handlers because we already installed the dependency injection package the instance will be resolved automatically but we have to mention something from the mediator version 9.0 the imediator interface is split into two interfaces isender and i publisher so even though we can still use the imediator interface to send requests to a handler if we want to be more strict about that we can use the i sender interface instead this interface contains the send method to send requests to the handlers of course for the notifications you should use the ipublisher interface that contains the publish method that said we're going to use the i sender interface instead of i mediator and also let's rename the field to sender also let's generate the constructor that initializes the field and let's make it a one-liner now we can continue usually we'd want to interact with a real database but for this video we are going to create a fake class that encapsulates this responsibility and simply interacts with some product values that said let's open a solution explorer and create a new class and name it product we need two properties here the public int id and the string name now let's add another class and name it fake data store here we start with the private static list of product and name it products then let's add a constructor we want to initialize our products list with the new list of products and add a new product with the id 1 and the name testproduct1 then we can copy this row and paste it two more times let's modify the id to two and three and also modify the number in the name to two and three now we need a couple of methods first let's add the public async method that returns task and let's name it add product it will accept a single product parameter inside the method we are going to use the products list and call the add method with the product as a parameter to add a new product to the list also since our method returns no value we will await task dot completed task second method is also going to be public async but it will return task innumerable product let's name it get all products as an implementation we will just await task dot from result and provide our products list as a parameter to return at this point we want to return to the program class and register this fake store as a service to do that we'll add builder dot services and call the add singleton method to register the store as a singleton service now that our data store is implemented let's set up our app for cqrs to start with it let's create three new folders in our solution so let's add new folder with the name commands then let's do the same and name it queries and finally the last folder with the name handlers we'll use these folders throughout the exercise to separate our models now let's talk about the most common usage of mediator requests mediator requests are very simply request response style messages where a single request is handled by a single handler good use cases here would be returning something from a database or updating a database there are two types of requests in mediator the ones that return the value queries and ones they don't commands to start with let's create a request that returns all the products from our fake date store in the queries folder we're going to add a new class and name it get products query let's remove this make it a record and inherit it from the i request interface that accepts innumerable product as a parameter this simple means our request will return a list of products that's why the irequest interface has that parameter then in the handlers folder we are going to create a new handler class to handle our query and name it get products handler to make this class a handler it needs to inherit the irequest handler interface we also have to provide two parameters for this interface the get products query the query that we want to handle and innumerable product as the type we want to return from our handler now let's implement the interface and leave it as is for now since we are going to use this handler to return the products from our data store we have to add a new private read-only fake data store fake data store field then let's implement a constructor and let's make it a one-liner at this point we can move on to the handle method implementation we can see that it returns the task innumerable product and has two parameters the request of our query type and the cancellation token the only thing we are going to change here is to make it async now in the method body all we are going to do is to return all the products from our store by returning await fakedatastore.getallproducts if we want we can make it as an expressionbody method to call our request we just need to add the get product section in our products controller so let's add the http get attribute first then we need a public async task action result action with the get product's name inside the action body we will return all the products by calling await and then using the sender variable and the send method where we provide a new get products query instance as an argument lastly we will return an ok result with our fetched products that's how simple it is to send a request to mediator notice we're not taking a dependency on fake date store or have any idea on how the query is handled this is one of the principles of the mediator pattern and we can see it implemented firsthand here with the mediator package now let's start our application and let's use postman to test it we have a prepared request and all we have to do is to send it and we have our result this proves that mediator is working correctly as the values we see are the ones initialized by our fake data store now to continue our video let's talk about the other type of mediator requests command to create our first command we are going to add a request that takes a single product and updates our fake data store so let's navigate to our commands folder and add a new class let's name it add product command we have to remove this and replace the class with the record we'll provide a single product property and we want this record to inherit from the i request interface notice this time the irequest interface doesn't have a type parameter this is because we aren't returning a value take note that due to the simplicity of this example we are using a domain product entity as the return type for our query and as a parameter for the command in real world labs we wouldn't do that we would use dtos to hide the domain entity from the public api if you want to see how to use dtos with web api actions you can watch our handling get requests and handling post put and delete request videos the links will be in the description now we need to handle this command to do that let's navigate to the handlers folder and create a new class let's name it add product handler again this handler must inherit from the irequest handler interface and we have to provide two parameters the add product command as our command and the unit when using mediator instead of void we use the unit struct that represents a void type now let's implement the interface here we're going to do the same thing we did with our previous handler let's create a private read-only fake date store fake date store field implement a constructor and make it one liner additionally we have to make our method async and await the fake date store dot add product method where we provide request.product as an argument we also have to return unit dot value the value property is the default and only value for the unit read-only struct with this out of the way we can return to our controller let's add an http post attribute first then we're going to create a public async task action result action named add product it accepts a single from body parameter of the product type and let's name it product now we have to use the send method again so let's add weight sender.send and provide a new add product command instance with our product that we receive in a request lastly we will return the 201 status code as a result now let's start the app in order to test it in our postman we will create another post request with the same uri as for the previous one we also have the headers added and the row body now let's send it and we get a 201 created response to verify this action let's return to the previous request and send it we can see all the products there so everything is working great now while this may seem simple in theory let's try to think beyond the fact we are simply updating an in-memory list of products we are communicating to a date store via simple message constructs without having any idea of how it's being implemented the commands and queries could be pointing to different data stores they don't know how their request will be handled and they don't care if we take a look in our post action we can see that it just returns a 201 status code but that is not enough most of the times there is much better way of informing our client that this action succeeded in order to do that we have to create get product by id action as well of course before we do that we have to create a new query record so let's navigate to the queries folder and create a new class and name it get product by id query let's make it a record provide an int id parameter and it needs to inherit from the irequest product interface from this we know that our query will return a single product as a result now in the fake datastore class we have to add another method let's make it public async task product and name it get product by id it will accept a single int id parameter and return a weight task from result where we fetch a single product from our products list with a single linqui method at this point we can navigate to our controller and add a new action we'll add a new http get attribute but this time provide two parameters the id of type int as part of the uri and the name for this action get product by id next let's create public async task action result get product by id action with a single int id parameter inside the action we'll create a product variable and await the sender.send method with the new get product by id query instance and id as an argument finally we return ok with our product with this in place let's open our add product command record and let's add a new product parameter for the irequest interface then we can navigate to the handler and replace unit with product here also for the return type and finally modify the return statement by calling request dot product now we can navigate to the controller and modify our action we'll add a new product to return variable to accept the result of the send method and then instead of this return statement we're going to call the created route method and provide the get product by id as the name of the action we are pointing to add a new anonymous object with a single id property which we assign from product to return.id and finally provide our product to return object as the last argument now let's start the app again and use our previous post request one more time we can see the 201 created result but also we can see a newly created product in the response body additionally in the headers tab we can find the location property let's copy that uri and send a request but this time we see an error message if you closely read the message you will see that it wants us to register our handler for this request as we remember for each query or command we have to create the handler but we didn't do it for this one so let's get back to our project navigate to the handlers folder and create a new class let's name it get product by id handler it must inherit from irequest handler and we have to provide the query get product by id query and the return type product now let's implement the interface we also need our private read-only fake date store fake data store field the constructor and let's simplify it and let's make this method async and return await fake datastore getproductbyid method with the request id as an argument we can simplify this one too now we can start the app send the post request again and once the result is here we can inspect the header step to see the location property let's switch to the get request and once we send it we can see the successful response we will not implement the put or delete requests since now you should be able to do it on your own and it is good for practice of course just pay attention that usually put and delete actions don't return a value in the response body just a 204 status code so create your commands and handlers accordingly in our ultimate asp.net core web api book we have a complete implementation alongside all the other web api features so if you would like to learn more feel free to visit the books page the link will be in the description below now let's continue towards the mediator notifications so we've only seen a single request being handled by a single handler however what if we want to handle a single request by multiple handlers that's where notifications come in in these situations we usually have multiple independent operations that need to occur after some event for example sending an email or invalidating a cache to demonstrate this we will update the add product command flow we created previously to publish a notification and have it handled by two handlers of course sending an email and invalidating a cache is out of the scope of this video but to demonstrate the behavior of notifications let's instead simply update our fake values list to signify that something was handled that said let's open up our fake data store and add a new method this will be a public async method returning task and name event occurred also we will provide two parameters the product product and the string event inside the body let's use our product list and call the single method to extract a product from that list with the id equal to the product id parameter and then set its name property to a custom string containing the product name value and the value of the event parameter lastly let's await task dot completed task now that we've modified our store let's create the notification and handlers to do that let's create a new folder and name it notifications then create a new class and name it product added notification let's remove the parentheses and make it a record then let's add a product parameter and finally this record must inherit from the inotification interface this is the equivalent of the request we saw earlier but for notifications to continue let's navigate to the handlers folder and create a new handler class let's name it email handler this class must inherit from the eye notification handler interface where we have to provide the product added notification this is the notification record that our handler is going to handle let's implement the interface now to continue we're going to add a private read-only fake date store fake date store variable and implement our constructor the same as before now let's make the method async and implement it with evade fake date store dot event occurred method and pass the notification dot product as a first argument and the custom email sent string as a second one also we will wait task dot completed task next let's navigate to the handlers folder again and create one more handler class named cache invalidation handler here we will have almost the same implementation as with the previous handler so let's inherit from the required interface provide the needed parameter and also implement an interface next we need our private read-only fake data store field and a constructor finally we can copy the implementation from the email handle class paste it here modify the string argument to cache invalidated and make this method async that's all to test this let's navigate to the controller to modify the post action before we do actual modification we have to use the ipublisher interface to publish our notifications so let's add another private read-only ipublisher publisher field add a second ipublisher parameter in the constructor cut this paste it into the body and initialize the publisher field now we can move on to the post action modification between these two lines we'll add weight publisher.publish and pass a new product added notification with the product to return as an argument if you wanted to we could have done this directly in the handler for add product command but let's leave it here for simplicity now we can start our app in postman let's first send the get request and we have our result next let's run the post request and as you can see the name is not just test product 4 but it has additions from both notifications now of course this is a simple example but the key takeaway here is that we can fire an event and have it handled many times without the producer knowing any different the identification is implemented in the final section we'll talk about something called behaviors in mediator often when we build applications we have many cross-cutting concerns these include authorization validating and logging instead of repeating this logic throughout our handlers we can make use of behaviors behaviors are very similar to asp.net core middleware in that they accept a request perform some action and then optionally pass along the request so let's see how to implement it first let's create another folder and name it behaviors also we will add a new class here and name it logging behavior we have to extend our class with two generic parameters t request and t response and then it has to inherit from the i pipeline behavior interface with the same t request and the response parameters now let's implement this interface and add an additional constraint with the where the request inherits from i request the response next we're going to add the logger in this class to do that let's add a private read-only i logger add the login behavior t request t response as a parameter and name it logger then we can create our constructor after the constructor is ready let's make our method async then with the help of the logger field we will log information and provide a custom string handling type of t request and extract the name then let's create a response by awaiting the next delegate and again we will use the logger to log information with the custom string handled type of t response dot name and finally return a response now in order to use this behavior class we have to register it in the program class we'll call builder dot services dot add singleton method and as a first parameter we'll provide type of i pipeline behavior and as a second parameter we'll add type of logging behavior notice that we are using this notation to specify that the behavior can be used for any generic type parameters to test this let's run our application then let's open up postman and run the get all products request now if we inspect the command window we can see some interesting messages this is the logging output before and after our get products query handler was involved the important thing here is we didn't need to modify our existing requests or handlers we simply added a new behavior and wired it up so that's it please don't forget to hit those like and subscribe buttons down there if you like the video and want to support us you can also use the bell button to get notifications from our channel also you can visit the code maze blog to download the source code and you can subscribe to our mailing list to get notified about our new content and videos thank you for watching and we'll see you again in another video until then all the best
Info
Channel: Code Maze
Views: 27,973
Rating: undefined out of 5
Keywords: CQRS, MediatR, ASP.NET Core, Design Patterns
Id: ykC3Ty-3U7g
Channel Id: undefined
Length: 39min 14sec (2354 seconds)
Published: Fri Jun 03 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.