[0:00]You've probably already heard the news that mediator is going commercial and this is unfortunate because I'm using it inside of my free clean architecture template. So, in this video, I want to show you how you can refactor away from mediator and build something yourself that achieves the same functionality. Here's a typical use case for registering a new user inside of my system and behind the scenes, this is using mediator. Now, the reason I have an I command handler interface is because I want to be explicit about the CQRS pattern which I'm applying here. And CQRS is short for Command Query Responsibility Segregation. Here's a very quick overview of how mediator allows us to implement CQRS. So, let's say we have our API, and we have some requests reaching our API endpoints. Now, broadly speaking, we can split these requests into commands. These are use cases or operations that modify something within the system. And we also have queries which are requests to retrieve some data from our API. Now, typically, how you're going to implement this with mediator is have a command or query object, and then a respective handler for the given command or query. And an implementation like this is where mediator excels. Another common pattern that you're going to see associated with this is the reaper pattern, which stands for request endpoint response. And it basically follows the same idea: you get a request object, it lands into your API or endpoint, and then you just return a response. Mediator or something like Fast Endpoints allows us to implement this pattern. Now, mediator also introduces some additional functionality in the form of pipeline behaviors, which are basically a middleware. But we're going to talk more about this in a future video. Now, how are we going to replace mediator with our custom solution? I'm going to split this into a couple of steps, and the first step is going to be creating a custom abstraction to wrap the mediator types. So, what are the baseline types that we have to take care of? Well, the first one here is the I request handler, and there is also the I request interface. So, we have a request object and then a respective request handler. Now, what I already did inside of my template is make my commands and queries explicit by defining the respective interfaces in the messaging folder. So, you can find I command, I query, I command handler, and I query handler. And they're just wrapping the I request handler interface and the I request interface without introducing any additional changes. So, this is a good thing because now my actual request handlers and my command or query objects don't need to know about mediator. You will see that they are not referencing mediator. This is going to make my move away from mediator slightly simpler. So, let's start with the simple I command interface and see what we have to do to support this. Now, this is an I request which is a way for me to define how I want to return my response, and in this case, I'm making sure that I always return a result. I also have a respective command handler, which is I request handler, wrapping my request type, which is my command, and then a result object which I'm always returning. Now, if we take a look into the I request handler interface definition, you're going to see that it just implements one method called handle. And it's a generic method that wraps the actual response that you want to return. So, in order to move away from mediator, we have to implement this method ourselves. So, let's define it here. Now, I just copied what we had inside of mediator, but in my case, I want to always return a result object for this type of command. Now, my generic argument is going to be a command, and then I can call this the command argument. Now, I also have to remove the I request handler reference, otherwise I would have two definitions of the same handle method. And now if I go to my I command interface, I can safely remove the I request result reference. So, with just these few changes in place, let's take a look at some implementations of this command. So, we have the complete to-do command and the delete to-do command. And if I go inside, you can see that both of them are still doing fine. They can compile and if we take a look at the respective command handler, let's say delete to-do command handler, you will see that everything here still compiles. And this is because we are using the same interface as we did before. We have a command object as the first argument to the handle method, and it always returns a result. So far, so good, but the problem is this currently will not work. So, how mediator works is it does some dependency injection magic behind the scenes, which is just it scans your assembly and finds any implementations of the I request handler and then registers that with dependency injection. Now, if you want to move away from mediator, you will have to do this yourself. So, what that could look like is saying something like add scoped, and then you specify your I command handler. So, let's say I first specify the complete to-do command as the interface definition, and then I have to specify the complete to-do command handler as the respective implementation. Let's do the same for the delete to-do command handler and then the delete to-do command. So, we're doing the service registration manually, but this is only part of the story. Let's say I go to the respective endpoint where I'm actually using this command, and you'll see that we are running into a problem. Previously, this was using the I sender interface from mediator because what this library allowed you to do was take a request object and a handler and connect them using a mediator object. At runtime, you could send your command and the library would find the respective handler, invoke it, and return the result. Now, in our implementation, we will have to do this manually, which does have some benefits. The first one being that we are making this explicit. So, instead of injecting an I sender, I have to inject an I command handler for the respective command. So, let's import the missing references, and then we have to specify the complete to-do command, and let's just call this the handler. And how my endpoint changes is instead of using the sender, I'm going to say handler handle, pass in the command object and the cancellation token, and I get back the same result object. So, now I can finally get rid of using mediator inside of my endpoint, and this completes my refactoring. Now, let's do the same in the delete to-do endpoint. So, right here, I'm going to align the arguments. Now, here I need an I command handler of the delete to-do command. Let's call it the handler, and I'm just going to say handler and call the respective handle method, and I can remove the reference to mediator. So, now I should be able to start the application and just confirm that everything is working the same as before. So, I'm going to place a breakpoint in the delete endpoint, for example, and if I go into the Swagger UI and fetch all the to-dos for the current user, and let's say we take this to-do item that I want to delete. So, I'm going to copy the identifier, head over to the delete endpoint, and then let's send this, and you can see that I hit the breakpoint inside of my endpoint. So, we didn't get an exception at runtime, and our handler instance is provided from dependency injection automatically. And you can see that the actual instance itself is the delete to-do command handler. So, our manual registration with the I is working as expected. We go into the respective handler where we are going to proceed to fetch the item and delete it. So, I can stop the execution now, because I'm not really interested in the result. And then let's discuss what our next steps are going to be. So far, we only migrated our simple I command definition, which only returns a result object, but we also have a generic variant which can return a concrete result, and we also have an I query. So, let's see how we are going to fix this. In the I command that has a T response argument, we just get rid of the I request interface. I can also remove a reference to the mediator and shared kernel namespaces. And I'll do the same in the I query definition, and it's just going to specify a T response. Now, I do have to fix the command handlers. We already know the drill by now: we need to define a handle method inside of our respective command handlers. So, in the I command handler that returns a T response, we are going to modify the handle method to return a result of T response. Now, you can obviously adjust this to how you want to use this custom mediator implementation. I prefer always returning a result object, but if you don't want to do that, then you can just omit this and return T response, which is basically how mediator works. Now, I don't want to break too much of my application functionality, so I'm going to use this version. And now I can get rid of the using mediator statement. Now, in the I query handler, I need to do the same. So, let's define a body, and I'm going to paste in the handle method that I have here. I have to specify the query here, and let's call this the query. And of course, I need to get rid of the I request handler. So, I'll get rid of the using mediator statement, and if I take a look at my use cases, for example, some query for fetching the to-dos, let's go to the get to-dos query handler. You'll see that everything here checks out. We didn't have to make any changes to the code because I was already using my custom abstractions, and I defined the same handle method that I was already implementing. The same goes for my query objects. So, the only thing that changes is the actual endpoint. So, if I were to go into the get endpoint for fetching my to-dos, then here we would have a problem. Now, the solution is again pretty simple: we just need to inject the respective handler. So, in this case, it's going to be an I query handler, and I have to specify the get to-dos query. Now, we do have to do some extra work in defining the actual response object as well, which we didn't have to for a simple command definition from the previous example. But the refactoring is more or less the same, and then I just remove the mediator reference. Now, of course, I have to register this with dependency injection, but I'm going to show you an improved way of how we could achieve this. So, what I have to do now is go through all of my API endpoints and apply this fix. So, I'm going to go ahead and do that. And once I've gone ahead and made all the updates to my API endpoints, my solution should finally build. Now, the downside is I still have to manually register all of my command and query handlers. Luckily, there's a way that we can improve this. So, to do this, I'm going to install an additional new get package. So, let me go ahead and look for a package called Scrutator, and I'm going to go ahead and install the latest version. What Scrutator is going to allow me to do is to simplify my assembly scanning code. So, I can say services and then call scan, and this is how you can start defining your Scrutator configuration. And then what I have to do is say scan, and for example, from assemblies of and then specify the assembly that I want to scan for my implementations. So, once I have this, I can start adding my classes, and let's say we want to find any classes that are assignable to our I query handler definition. Now, as this has two generic arguments, you have to specify a comma here. And then I also want to pass in another argument to also look for internal types. Once I complete the call to add classes, I will have access to all of these types that match my filter, and then I can register them as implemented interfaces, and this is going to basically be my query handlers. And I can also specify the lifetime.
[13:51]I made mistake here. You have to specify the lifetime for each type: WithScopedLifetime(). I'm actually going to go ahead and copy this command that I just entered and let's drop it in twice, and the reason is I need to do the same for my two command handler interfaces. So, the first one is the regular command handler without two generic arguments, and the second one is the I command handler with a T command and a T response object. So, now that I have all of them picked up by Scrutator, I can go ahead and say with scoped lifetime. Now, I'm still going to leave the call to add mediator in place, but I will get rid of my manual registration for the two command handlers. So, let's place a breakpoint here and here, and then start the application again. So, you're going to see that I hit my breakpoint, and the services collection contains 130 services. So, let me press continue, and when this call completes, we end up with 139 services. Now, if I take a brief look at what we added to the service collection, and I have to scroll all the way down, you can see our nine services that we just introduced. You can see that the service type is I query handler and a generic argument. So, this represents our query objects. And then we have some I command handlers and the respective command objects. So, we pretty much did what mediator does here with a couple of lines of code and Scrutator. And we can additionally hide this behind an extension method to simplify our setup even more. So, now we've completely moved away our request handlers, or to be more precise, the query handlers and the command handlers, do not use mediator. Now, to show you that everything still works, I'm going to just test this out quickly. So, let's send a request to fetch the to-dos for the current user, and you can see that we hit our breakpoint, and we're going to execute the respective handler and return the response. So, let's send a request to delete a to-do item, and we land on the respective breakpoint. Now, as this is already deleted, the database is going to return null, and we're going to return a failure result from our handler. So, you can see that both the query and the command flow work as expected. Now, we still have a problem, and that problem is cross-cutting concerns, which mediator solves with pipeline behaviors. We were using them to implement request logging and validation, and our current implementation doesn't support this. I'm going to cover how to migrate the cross-cutting concerns in a future video, but if you want to tackle this yourself, you just have to implement the decorator pattern. Now, if you want to grab the updated clean architecture template that doesn't use mediator anymore, check out the pinned comment. It's going to be right below this video. Also, take a look at my courses if you want to improve your software architecture skills, and until next time, stay awesome.



