how do you build a modular monolith and what does this architecture actually mean well in this video I'm going to explain what a modular monolith is and I'm going to show you a solution structure of a project implemented using the modular monolith architecture so let's start by explaining what a modular monolith is I'm going to read a theoretical explanation that says that a modular monolith is a software design approach in which a monolith is designed with an emphasis on interchangeable modules and modules is really the keyword here that explains how you build a modular monolith architecture definitions are definitely useful but I believe more in examples so let's take a look at this explanation on this side we have a diagram of a monolith system for some eShop application implemented using asp.net core you can see that it has a single database which is post SQL and the idea behind this diagram is to represent the module as a single deployment unit while the microservices system on the other site which implements the same functionality consists of physically separated components or microservices so we have the catalog service the order service the customer service and the collaboration service so all of these services are essentially modules or bounded contexts and in the case of microservices these modules all also represent the physical boundaries in the system but do these boundaries actually have to be physical and you end up with a distributed system well the answer is no and this is how you can Implement a modular monolith system so you can see that what we had as Individual Services in a microservices architecture are now modules inside of our monolith system so the boundaries are now still there you can clearly see them around each modules except in this case they are logical because we are in the same deployment unit or in other words running inside of the same application what's also different is that we are using a single database so we have to be careful about how we share the data between these modules and in reality there are three tough problems to solve when it comes to modular monoliths the first one is defining your modules or bounded context which means getting your boundaries right the second one is how do you solve the communication between these modules you'll see that this actually has many implications between how you integrate them together and the last problem that you need to solve is how do you make the data between your modules independent and isolate it from the other modules in the system I'm going to explain how to solve these challenges on an actual modular monolith implementation so let's take a look at the project solution here I have the trainer sphere application which is implemented using the modular monolith architecture and you can clearly see that I have three distinct modules in the system one is for the users the second module is for training and we also have a technical module that handles the notifications so let's focus on one of the modules and I'm going to use the users module because it will be the simplest one to explain if you take a look at the projects inside of this module you will notice that they actually follow the clean architecture so I've got the domain project the application project the infrastructure and persistence project but I also have an endpoints project which would be the presentation layer in a clean architecture so you may come to the conclusion that a module is almost like a standalone application however it's not really the case this separation is only logical and we really only have just one executable application which is a web API but let's examine the users module starting from the domain layer I have the base user entity inside and this is just your typical domain driven design entity let's for example take a look at a method that I have here such as the create method which is responsible for creating a new user instance assigning it some roles so inside of these modules we're also taking care of roles and permissions which is part of our authentication and authorization responsibilities but we found a way to represent this nicely inside of the domain then we are also raising a domain event and you can see that this domain event contains quite a bit of information and lastly we just returned the US user which somehow gets persisted into the database other things that you will see are the contracts for the domain events and these are different from integration events which I'm going to mention later and then after the domain layer we have the application layer which implements the actual use cases so let's take a look at the register user use case now this might seem a little confusing because what I'm using here is Railway oriented programming which is essenti Al a way to write functional code in t while having an elegant way to represent failures in the flow of my use cases so to break down what's Happening Here is we are calling the user create method in the domain running some checks to see if we can actually register this user and then taking care of persisting this user in the database I won't go into too much detail about railway oriented programming as I have a video where I talk about this from scratch but what I like about it is that I find this kind of code very readable because you can clearly see how each step flows into the next step in the use case and in the end you just have a way to represent what is the final result of the flow and another benefit is at any point I can fail the execution Chain by simply mapping to a failure result using an explicit error and when one of these methods fails the other ones will not execute and will just return back the failure result so you can see we have some side effects here talking with the database but I also mentioned that we have the domain events and in this case the user registered domain event which will be published after this use case is completed and this will be handled in the user registered domain event handler and what's going to happen here is we are just going to take the domain event instance and convert it into an integration event and we're going to publish this integration event using an event bu which in this case is implemented using the mass transit in memory transport and this is how the modules in the application communicate between each other so there is no direct calls between the modules and if you take a look at the project references in the application layer you'll see that I'm not referencing any of the modules explicitly except the integration events project of these modules so if I make a slight detour into the integration EV events project this is a project that simply contains contracts or classes or in this case records for my integration events so I've got an user changed integration event and this is something that other modules in the system will be interested in the other one is the user registered integration event which you saw was published from my domain event handler so what is the difference between domain events and integration events another way to look at them is internal and external events where domain events are handled and used inside of a single domain or bounded context or in our case a single module and integration event are meant to be published and consumed by other modules in the system hence the name external events another term that you're going to hear in the context of the modular monolith architecture is the public API and in this case my integration events represent the public API of of my module and these integration events are the only things that are allowed to leave my user's module and to be consumed by other modules in the system so you saw earlier that the application project has a reference to the training integration events and I'm going to show you how this is used in the user registrations feature folder I have an events folder here and you can see I have an invitation sent integration event handler so this is now a Handler for our integration events and in this case I'm handling the invitation sent integration event this integration event will be published by the training module and then the user's module is subscribed to this event and handles it as soon as it's published and in the handle method what's happening is it's going to create a new user registration and then process that in the database I also have another integration event handler for the invitation cancelled integration event that's going to look for a specific user registration and attempt to cancel it because it was already cancelled in the training module so each module has its own domain and its own application layer with the use cases it uses the integration events to communicate with other modules which means that we are embracing an eventually consistent communication approach but what about the persistence each module is also going to have its own set of database tables and in this case I'm using my n framework cord database context to make this explicit so you can see that my users module is going to have its tables defined in the users schema and this also applies to the other modules that also have their own distinct schemas the other things that you will see inside of the persistence project are repository implementations database migrations and fluent configuration classes for EF core now let me jump into the database for a moment and show you some entity relation relationship diagrams so here's the entity relationship diagram for the users module and you can see the users table here as the central table in this module the other one is the user registrations which we mentioned when we discuss the integration events that we were handling from the training module the other tables are related to processing inbox and outbox messages these are patterns that you can use to implement reliable messaging in your system and you can also see the tables related to roles and permissions which I use to implement permission authorization in the system now let's take a look at the training module which has the concepts of the trainers the invitations which we mentioned when we discussed the integration event and the clients and then again we have the outbox and inbox tables and the notifications module is the simplest one it doesn't have any internal tables it only reacts to the integration events which it process say through the inbox and then proceeds to send the appropriate notifications one more thing I want to highlight is how I'm using dependency injection here and many other external services such as in my domain event handler I'm using the concept of an event bus well where is all of this registered for my specific module this is where the infrastructure project comes in which acts as the composition route for the module and here you can see the implementation of an IM module installer interface in this case is going to take care of the users module I'm registering most of the services using assembly scanning and these specific registrations are inside of my service installers folder most of the things here are your basic dependency injection code such as registering mediator and pipeline behaviors but one thing I want to highlight is this endpoint service installer which takes care of adding the end points for my module under the hood I'm using controllers to to Define my endpoints and I'm registering them by calling the add application part method this allows me to have my modules endpoints separate from the web API and I can Define them inside of this endpoints project so let's take a look at a few endpoints for example for the user because that's what I'm focusing on so here is the definition of the register user endpoint I'm using the ESS API endpoints library to Define my API endpoints inside of a single class and the purpose of an endpoint is just to create a command or a query and send it using mediator and then it's going to convert the response which is a result object into an appropriate action result I also have some attributes for Swagger documentation and which response type this endpoint produces then I'm defining a route and if I have any specific permissions they're also going to be defined on my endpoint let's take a look at the update user endpoint which is slightly different so the basic flow is the same we accept a request convert that into a command and send it using mediator but I want to show you how I'm using authorization here so first of all I have the authorized attribute that enforces the same user policy this is a resource authorization check that's making sure that the user calling this endpoint is the same user that's being updated and then the second attribute I have is a has permission attribute this is a custom attribute that allows me to Define c custom permissions that are required to be able to execute this endpoint in this case the permission that I'm looking for is modify user then let's take a look at a get endpoint such as this one again you'll see these authorization checks and we just take the user identifier from this endpoint create a query instance and send that using mediator so we discussed all of the important projects inside of a single module and if I show you the training module you'll see that it follows the same pattern following the clean architecture structure and exposing the integration events that are part of the public API and now I want to move into the web API which is the glue that's going to stick all of this together and the important part is here where we are going to install the services for these modules again I'm using assembly scanning and I'm going to look for my users module training module and notifications module and execute the module installers in the infrastructure projects of these modules this is going to take care of dependency injection and also registering my controllers for each of the modules and the rest of the calls here are setting up Swagger introducing Cog logging authentication and authorization mapping my controllers and just running the application if I Circle back to the training module I want to highlight how it's interacting with the users module with the integration events that it's handling so here's the user registered integration event handler and inside of it we're going to take this event and create a new trainer instance so if you think about this the user Concept in the users module now becomes the trainer concept or enti in the training module and one more way to know that these are the same entity is the fact that I'm using the users identifier as the identifier of the trainer in the training module and this is what a bounded context means in practice you will have the same concept in your domain represented in different ways depending on which bounded context we are currently in in any case we just scratch the surface of the modular monolith architecture and I'm going to be making a lot more content about this in the coming months and if you want to learn more here's a high level introduction to the modular monolith architecture that you can watch next make sure to smash the like And subscribe buttons and until next time stay awesome