Unit of Work Is Even Better With MediatR + TransactionScope

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
if you are using the unit of work pattern with cqrs you're going to end up with a lot of duplicate code in your command handlers how do we usually solve code duplication we do that by creating an abstraction so in this video I'm going to create an abstraction with the unit of work and mediator's pipeline behavior let's first take a look at the create order command Handler you'll see that it's depending on a few repository interfaces and then we have the unit of work abstraction if we take a look at the handle method you'll see that the repositories are used to fetch the data from the database insert new entities into the repository and then we use the unit of work to persist the changes if I go to the remove line item command Handler which is injecting the order repository and the unit of work again you'll see the same pattern repeated so we fetch something from the repository we do some business operation and we call Save changes async if I take a look at the few command handlers for the products let's say create product command Handler you'll see the same pattern again we do something with repository we call Save changes let's take a look at the update product command Handler we fetch something from the database perform some update and we call Save changes async and the same applies in the delete product command Handler we fetch the product delete it using the repository and again we call Save changes async so the general pattern is perform some sort of business operation and call the unit of work to persist this in the database now wouldn't it be great if we could get rid of this call to the unit of work and move it someplace else so that it's always called and we don't need to think about it in our Command handlers Well turns out we can do that because we are using mediator and we're going to see how to create a pipeline Behavior implementation that's going to wrap the current request in a unit of work transaction let's start by creating a folder which is going to hold our behaviors and I'm going to define a new class inside which is going to be my generic pipeline behavior I'm going to call it unit of work behavior I'm going to make this class public and sealed and we have to implement the ipipeline behavior interface which is coming from mediator this is a generic interface with two generic arguments the request type and the response type so let's add those to our unit of work Behavior the request and T response and we're going to pass them to the ipipeline behavior interface and what's missing is the generic constraint for the T request that it must not be null so let's go ahead and Implement our interface and you'll see that there is one handle method and the Delegate for the request Handler that we are about to invoke before I implement the unit work behavior let's register this class with dependency injection I already have a call to add mediator here which is registering my request scandlers which are my command and query handlers and to register the pipeline Behavior we need to call the add open Behavior method which is going to allow us to register our generic Behavior we're going to specify our type by calling type of and specifying the unit of work Behavior class so this takes care of registering this Behavior the slight problem with this approach is that this is a wrapper around any kind of mediator request and this is going to cover both our commands and our queries because I only want to run this behavior on my commands I'm going going to add a filter at the start that is going to check if the current request is a command you could introduce some sort of marker interface that is going to check if this request object is a command like an I command interface or you could rely on your naming convention my naming convention assumes that all of my commands are going to have their name ending with command so I can say something like this type of T request I can take the name of the type now and I can check that it ends with the word command and this is enough to verify that the current request is a command or it is not so let's say if this is not a command I'm just going to call the next request Handler delegate by calling await next this is going to continue my pipeline otherwise let's take care of our Command Handler and the unit of work Behavior so obviously we need to start by injecting our unit of work so let's go ahead and do that so you need to work and we inject it from The Constructor and right here what we want to do is we want to Simply invoke our Command Handler and we want to say you need to work save changes async and we pass it the cancellation token now because we need to return some sort of result we can store the result of this call into a variable so whatever is the response and after we have called unit of work save changes async we can return this response to make this a little bit more readable we can move this check into some sort of method which we can call either is query or we can call it is not command so whatever works for you if this is not a command just await the request Handler delegate and return otherwise we awaited and store the result we call our unit to work and persist the changes and only then do we return our response what does this mean for our Command handlers let's take a look at them one by one and we can start removing this call to unit of work save changes async this also means that we can drop the dependency on the unit work interface from our class and from our Constructor so this is what we are left with let's do this also in the remaining command handlers so I'm going to be a little bit quicker now with removing these next up is the create product command Handler so let's get rid of this and we can also make this not asynchronous but if we do that we need to return some sort of task so we can say return task completed task or you can leave it asynchronous but there's really no point if you want to be awaiting anything inside let's go to the create order command Handler get rid of the call to save changes async and remove our dependency on the unit of work and the only thing that's remaining is the remove line item command Handler let's get rid of the unit of work call here and the dependency in our class so what we are left with is just this we are using the order repository fetching the order from the database removing the line item and we are relying on our unit of work Behavior to persist these changes the potential problem with this approach is that persisting to the database is implicit and you have to be aware of what's going on behind the scenes but it's going to spare you writing those calls to save changes async we have to note that this only works because we are using EF core and EF core has a change tracker which is tracking all of the changes that are made in a single transaction if we take a look at this example we are adding an order and another summary in the same request and EF core will take care of persisting these in a single transaction to the database so we won't run into the situation where one of these is persisted and the other one is not we have an atomic trans action because we are using a SQL database another issue that you could run into is having to return some sort of result from your command Handler a common situation is returning an ID of the newly created entity in this example that would be the order ID in my implementation the order ID is already generated at the Domain level because I'm using a good value which I can create in memory but if you are relying on database generated values then you have a problem there are two ways how you can solve this I'm going to show you both of them and then you can decide if any of these are worth your time one is to use the Hi-Lo strategy for generating your primary keys so let's say if you were using some sort of numeric value as the primary key like a long or an integer you can go to your application DB context and inside of the on model creating method you can configure EF core to use the high low strategy for generating your primary Keys what this is going to do is EF core is going to fetch a set of values for the primary key and EF core is going to store them in memory and use them when you add new entities whenever a batch of keys is used EF will fetch another batch and if your application stops and restarts then the current batch is lost and EF is going to just fetch another one if this is not something you want to do then you are left with no choice but having to use the unit of work here so what that would look like is let's just bring back the unit of work that we had before so we are back where we started and after you call unit of work save changes only then you can say something like return order ID so this is in the case where you are not using a primary key which you can generate in memory the downside to this approach is you have one call to save changes here it's quite possible that you could introduce some other logic here and then rely on your call to save changes in the unit of work behavior and this is a big problem because this is not going to be one transaction but two of them either one of them could fail if the first one here fails then you're good nothing is going to be wrong but if this one succeeds and then the unit of work Behavior call fails then you're going to end up in an inconsistent state so the solution is just to create a transaction at the unit of work Behavior level to create a transaction you either need to expose something on the unit of work another option how you can achieve this is by creating a transaction scope so let's go ahead and use that approach I'm going to create a new transaction scope instance of course I misspelled the name so let's rename this to transaction scope and then we need to move our code inside of the using statement now it's very important after we call Save changes async that we also call transaction scope complete this is going to create a global transaction and your EF core queries are going to run inside then you can go ahead and invoke your request Handler and if you have calls to the save changes method inside they are going to be running inside of a transaction that is the same one that this call in our unit of work behavior is running in after we have completed everything we call transaction scope complete and this tells the transaction scope that we are done with our changes and we can commit this transaction to the database what happens if something goes wrong this is why it's so important to define the transaction scope inside of a using statement and when the using statement goes out of bounds the transaction scope will be disposed and if there are any exceptions the transaction scope will roll back making sure that we have an atomic transaction I'm curious to hear what you think about using this approach with the unit of work Behavior so let me know in the comments down below also make sure to smash the like And subscribe buttons and until next time stay awesome
Info
Channel: Milan Jovanović
Views: 21,559
Rating: undefined out of 5
Keywords: unit of work, unit of work in asp.net core, unit of work repository pattern, unit of work dapper, unit of work entity framework, unit of work in asp.net core web api, unit of work repository, unit of work ef core, unit of work pattern, unit of work clean architecture, unit of work cqrs, unit of work ddd, unit of work mediatr, unit of work mediatr pipeline, unit of work pipeline behavior, unit of work transaction, unit of work scope, transaction scope, transactionscope
Id: sSIg3fpflI0
Channel Id: undefined
Length: 13min 29sec (809 seconds)
Published: Fri Jun 02 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.