Domain Events | Clean Architecture & Domain-Driven Design from scratch | Part 17

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
what's up everyone amihai here in today's video we're going to talk about domain events we're going to see why this is such a crucial and fundamental topic when it comes to domain-driven design and how the domain events pattern allows us to actually communicate between various Aggregates a domain event is simply an event or something that happened within our system something that's interesting from a business point of view and it's defined in past tense so because we're working on a dinner hosting platform then the types of events that we can expect to have are stuff like menu created dinner created dinner reserved dinner updated and so on now the beauty of doing event storming is that after event storing the artifact that we have contains all the various domain events the commands the Aggregates everything we expect in the process that we modeled in our case it's this process and then we're able to extract from the event storming artifact all the various entities that we have and we can go ahead and model our domain layer and Define all our Aggregates and entities if you missed this video then I'll link it over here so you can catch up up until now we have the various Aggregates we have the entities we have the relationships that they have with one another but now the question is how do we have changes to one aggregate affect another aggregate so for that let's look specifically at the dinner and the menu so like we said every dinner has to be associated with some menu that's why the dinner has the menu ID that it's Associated to and the menu has all the various dinner ideas that are associated with this menu now the question is how do we when we create a new dinner how do we add it to the list of dinner ideas over here the reason why this isn't a trivial question is like we said in domain Dragon design Aggregates are transactional boundaries meaning that changes to this area cannot affect this aggregate that's the reason why the the dinner ideas over here aren't foreign keys that are associated to the actual dinner meaning that we can't just create the new dinner stored in the database and have this dinner ID added to the menu aggregate but we need to think of something else okay that's where domain events come into play where what we do is each entity in our system can store zero or more domain events and then when we're storing the aggregate in the database then what we'll do is we'll extract from the aggregate all the various domain events and we'll publish them then we'll have some event handler that will listen to that event in our case it's going to be the dinner created domain event so we're going to have some Handler listening to that event and adding the dinner ID to the menu okay so that's how Aggregates communicate with one another so basically when you want changes to one area to affect another irith the way you do it is you creative and handlers and they they apply the changes that you want to be applied in this video we're going to focus specifically on the menu created domain event inside the menu I read so over here we have our file system or the relevant files in our file system and we're looking at the flow of creating a menu so we'll invoke the create menu method in the menus controller in the application layer which in turn will invoke the create menu command Handler in the application layer so over here in the domain layer we have the menu area so all the command Handler needs to do is to create a new instance of the menu aggregate okay so all of this we already have in our application it exists from beforehand what's new in today's video is it that the menu aggregate as an entity within our system can contain zero or more domain events and over here each one of our entities will have a new folder other than the various entities and value objects it will also have a list of domain events and these are the events that can happen within in this aggregate or the entities that are within this aggregate then when we do the actual operation on the aggregate it will create the corresponding domain event which will be stored within the list of the main events in the aggregate then when we call Safe changes on our DB context in our infrastructure layer we're going to be invoking the published domain events Interceptor which will be in charge of pulling the various domain events and Publishing them via mediator so each one of the domain events in our system is going to be a notification or a mediator notification and over here we're going to publish them to the mediator publisher and what's going to happen is it's going to invoke the corresponding handlers in our case we're just going to be creating a dummy many created event handler which will be invoked and will do something just so we see that it works all together so that's what we're going to be doing today 8 so let's Jump Right In and implement it in the Uber dinner application okay so the focus is on the domain layer so inside here we have in the models all the various base types that we need what we're going to be adding to here are two things the first one is the domain event interface so let's start with that so I domain event okay so we have our domain event interface let's go to the entity and like we said let's create here a list of domain events so let's say over here private read-only and this is going to be simply a list of I domain event and we'll call it domain events let's initialize it with an NT list so this is never an invalid null State and let's create a way to retrieve the list of domain events so let's over here public I read only list yes this one I domain event and let's call it domain events and what this will do is we'll simply return the domain events as read only the next thing that we want to do is we want to actually create the domain event that will be stored inside this list when it occurs so for that let's go to the menus Aggregate and over here alongside the entities in the value objects let's create a new folder and let's call it events inside here we're going to create the menu created domain event I'll say menu created and this will be a simple record where we're going to have the menu create domain event and this is going to hold the menu that was created and because this is a domain event so let's inherit from the I domain event interface okay so that's it we have the menu created domain event we have the list of domain events in the entity all we have left to do is in the menu when we're calling the create method then over here we also want to add the domain event to the list remembrance so Bar menu equals new menu and let's return it over here and then once we have the menu instance then let's add it to the list of domain events that the menu has so let's say over here add domain event this is a method that we're going to create in a minute and let's say over here new menu created this is the record that we just created and let's pass it the menu because as we said this contains the menu that was just created so we have this let's create the add domain event method now because this is something that all the entities are going to have so let's go back to our entity Base Class over here and let's add this method so down here we're going to say public void add domain event and it's going to receive a i domain event domain event and we're going to add it to the list of domain events that we have over here so let's say over here simply domain event and that's it okay so this is basically what we're going to have throughout our application so in one of the entities something is going to happen in this case we're creating a new menu so what this is going to do is it's going to create the corresponding domain event and edit to the inner list of domain events inside that entity now all we have left to do is to extract the list of domain events publish them within our system and that will invoke the corresponding event handlers that will do whatever they need to do again for each event we may have multiple event handlers that's why we're using the mediator notification which supports having zero or more notification handlers so let's see how in the infrastructure layer we can pull all the various domain events and publish them so inside the persistence folder in the infrastructure layer we're going to create here a new folder and we're going to call it interceptors and over here we're going to create the publish domain events Interceptor so let's say over here public class publish domain events Interceptor and this is going to implement the save changes Interceptor from antifremacore and if we look at the base class there we can see it has here all these various methods like saving changes which is called right before it saves the changes or the saved changes which is called right after saving the changes in the database and there are also some other methods here for when the saving changes failed and so on so what we're going to do is we're going to override two methods over here which is the saving changes and saving changes I think so let's say override saving changes and also saving changes I think this one over here and what we want to do is that when we call Save changes inside our application so before storing it in the database that's because we want it to be included in the same transaction we want to extract all the various domain events and publish them then all the inner or recursively inner domain events that will be published and make changes to the database will all be inside the same transaction then if this transaction fails what's going to happen is that everything will be rolled back and no changes will be applied meaning that all our domain events and their side effects will either be applied together or they won't be applied at all now it's important to notice that if you have handlers that for example send an email then you may send an email and the transaction will fail the database won't be updated and the email has been sent for those cases there are a few patterns that we can follow or a few approaches that we can take but since I'm not going to be covering them in this video I just want you to keep in mind that when you're defining your domain events and the corresponding event handlers you need to remember that all we're talking about are changes to the database so if you have anything else that's happening which is not making changes in the database then it doesn't fall under the umbrella of what we're talking about today because in the end what we want is that within one transaction all the changes to the database are going to be applied or not applied okay this is a solution that I think is suitable for most applications because it doesn't require extra ceremony that other Solutions require but please remember that you're also paying the latency of applying all the various side effects so if you can't tolerate the extra latency in replication then please wait for future videos because I'm going to be covering the various other options that we can take over here instead okay so with all that out of the way let's continue to what we still have left to do over here which is extract all the various domain events and publish them so over here let's say private void publish domain events and this is going to receive the DB context and we'll see in a moment how we're going to get hold of the DB context so let's say the context over here and what we're going to do is we're going to be calling this method both from the saving changes in a synchronous matter and from the saving changes async in an asynchronous matter now something that we can do is we can simply throw an exception from the saving changes method and that way we're enforcing that everyone in our system must use the saving changes async method because that's of course what you want to do in i o operations but in our case I'm just going to be leaving it and you can do whatever you want just so you have both of the examples so the way we're going to be calling it over here is the following so first of all it's changes to task and let's say the following so over here we're going to be calling the method and we can get the DB context from the event data dot context so let's say get a waiter and get result and over here we'll do the same thing but instead of get away or get result we're going to await the response and adhere async and now we also want to say async over here and to await calling the base method and then we have the publish domain events method which what we want here is the following so first of all we want to get hold of all the various entities then get hold of all the various dominance then we want to publish the domain events right so over here we can say publish domain events and lastly we want to clear the domain events from the entities so they're not published twice right so clear domain Events first of all let's check if the DB context is null if it's null then let's simply return over here and not continue we don't need to do anything next let's get all the entities so let's say VAR entities and let's say DB context let's use the change tracker to get all the entries of a specific type okay now it would be nice if we can simply write over here entity and get all the objects in the change tracker of type entity but we can do that because we have this tid over here and I don't know of an easy or pretty way to do this using anti-framer core and reflection so what we're going to be doing instead is an approach that I first saw used by Jason Taylor if you're not familiar with his clean architecture template then I'll link it over here make sure to check it out it's not what he's doing today in the template but this is something that I think is the best solution given our application so let's see what this looks like let's go back to our domain layer and in our domain layer under the models let's create a new interface and let's call it I has domain events so public interface I has domain events so first of all we're going to have over here the list of I read only of domain event and let's call it domain events and let's say get and the second and last method we're going to have over here is clear domain events which as the name suggests will clear the list of domain events we have over here and this is all we need to retrieve all the various entities and afterwards clear the list of domain events the next thing we want to do is we want to go to the entity and over here add that implements the I has domain events then this over here is the domain events that we just talked about in the interface and we still need to implement the clear domain events method so let's go ahead and add that so all we want to do is we want to say domain events and clear and that's it then what we can do back in the Interceptor is say the following let's retrieve from the change tracker all the entries of type I has domain events then over here let's take only the entries that the underlying entity has zero or more one or more doing events I'll say any and then let's select only the underlying entity right that's false so now we have over here the entities with domain events and now that we have the entities with domain events then let's get hold of all the various domain events so let's say over here domain events and you can get all the entities with domain events that's to list it over here so this is actually executed so from the entities with domain events let's select many and we want to flatten the list of domain events so I'd say entry dot domain events and let's to list this so we have a new list with all the domain events okay now that we have this the question is did you notice the bug that we have over here so if we publish the domain events this will execute the various event handlers and then the event handlers when they arrive over here the entities will still have the domain events in them and it'll be recursively invoked until further notice so what we want to do is we want to switch the order over here and the first thing we want to do is first to clear the domain events from the entities and only then publish the domain events so let's go ahead and do that so let's say entities with the main events and for each of them we want to take the entity and we want to clear the domain event now finally we can publish the domain events I'll say for each domain event in domain events let's use mediator to publish them so let's get a hold of our mediator using dependency injection so that's a private read-only I mediator mediator and let's create a Constructor and inject it and actually we can use the I publisher interface following interface segregation principle because I Publishers all we need so let's take only the I publisher let's do it also over here and now that we have the mediator let's go ahead and say mediator publish domain event and of course that's await this call and that should be it for this file now that we have the Interceptor the question is how do we actually have it invoked every time we call Save changes so for that what we want to do is the following let's go to the dependency injection of the infrastructure layer and over here where we have ADD persistence let's also add the Interceptor to the service collection so I say publish domain events Interceptor and now that we have this let's go to the Uber dinner DB context and inject it over here so that's a private read-only publish domain events Interceptor publish domain events in terms of term and let's add this as a parameter and inject it like so and now that we have this we want to override the on configuring so let's say on configuring and over here we have the options Builder that allows us to add an intercept term so let's say add Interceptor and let's add the publish domain events interceptor okay another thing that we want to do over here is in the model builder we will never want to store the list of domain event in the database so we can simply say over here ignore list of I domain events and that's it the last thing that we're missing so if we're looking at the Interceptor so over here we're publishing the domain event but if we look at the I domain event interface we haven't defined that this is a mediator notification for that let's go ahead and say over here that this implements the I notification interface and let's add mediator to our domain layer because we haven't done it yet so I'll say net add yes to the domain layer the package mediator okay now side note I updated our project to use mediator 12 from mediator 11. so if you're following along make sure to follow the migration documentation on the GitHub repository okay so we have mediator let's add it over here and now we have everything that we need let's make sure that this builds successfully and also let's Implement a an event handler to make sure that this works end-to-end like we expect so let's go to the menus folder in the application layer and alongside the commands and queries that's all also creates here an events folder and over here let's create just a dummy event handler so public class dummy Handler and this will implement the eye notification Handler for mediator and the notification that we want to subscribe to or we want to handle is the menu created right so I'll say menu created and let's implement this interface and over here we get the domain event that was invoked so we can use it as we want so in our case it's the mini created that has internally the menu that was just created and for now let's simply return task dot completed task let's put a breakpoint over here let's run the application and make sure that everything works as we expect okay the debugger is attached let's make the request and we hit the command Handler over here let's continue and we see that we arrive at the create method in the menu Aggregate and over here we can see that it was added successfully to the menu arrogates so we can see we have the domain events and we have over here the menu created event if we continue then we arrive at the publish domain events the DB context isn't expected to be null and it isn't if we step over this then we should have here a single entity that has a domain event and this is indeed our menu with our mini created event over here over here we're publishing the domain event so let's click continue and we arrive at our dummy Handler and over here we have our domain event that internally has the menu with all the details that we expect but of course now the domain events is zero because we cleared the domain events click and play we can see that this end successfully and we get the response back okay so we have domain events in our application this is such a crucial piece of the puzzle when it comes to creating a domain driven design project now I know if you're following this for the first time or even not you're thinking to yourself that there's a lot of boilerplate in our application needed to put it in the correct structure for domain driven design this is true it's a big big overhead when creating a new project but I also hope that you're able to see how this creates structure so if we're looking at the application layer let's look specifically at the menu then we can see that we have over here the commands the events and the queries and if you're simply opening this application or an application built following this strategy then all you need to do is open the application layer you can see all the various usage use cases that you have you can see the commands and queries and the various events they have to do with this specific entity this is a visibility from a folder structure point of view that you don't have in other applications so if you're working on a project that has many teams many team members that are working on the same code base this enforces some structure that is easy to follow and I hope that you can imagine how as the application grows this stays more or less consistent as the solution grows not only does it stay structured but also because we modeled our domain layer correctly we don't have dependencies between our Aggregates and we can simply take a slice from our application and move it to a different application or different bounded context as the application grows and we need to refactor and simplify each one of our domains so I hope this is starting to make sense and your head doesn't hurt too much so that's it for this video I hope you enjoyed it and you learn something new let me know in the comments smash the like button smash the Subscribe button and I'll see you in the next one
Info
Channel: Amichai Mantinband
Views: 23,717
Rating: undefined out of 5
Keywords:
Id: MhoFCy_2-wQ
Channel Id: undefined
Length: 24min 33sec (1473 seconds)
Published: Mon May 01 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.