# I Built the Same App as a Monolith and a Modular Monolith

## Метаданные

- **Канал:** Milan Jovanović
- **YouTube:** https://www.youtube.com/watch?v=S2zqdX411RQ
- **Дата:** 15.05.2026
- **Длительность:** 11:23
- **Просмотры:** 7,341

## Описание

Want to master Clean Architecture? Go here: https://dub.sh/clean-architecture
Want to master Modular Monoliths? Go here: https://dub.sh/modular-monolith

Join the .NET Architects Club: https://www.skool.com/mj-tech-community-5418/about
Get the 2026 .NET Developer roadmap here → https://the-dotnet-weekly.ck.page/2026-roadmap

What is the real difference between a monolith and a modular monolith?

A regular monolith can still be well-organized. But a modular monolith goes a step further by enforcing strict boundaries between parts of the system.

In this video, I compare the same application implemented as a traditional monolith and as a modular monolith. You’ll see how coupling slowly creeps into a codebase, why folder structure alone is not enough, and how module boundaries help you keep your architecture flexible as the system grows.

You’ll learn:
- What makes a module different from a folder
- Why monoliths often become tightly coupled over time
- How modular monoliths enforce boundaries
- Why public APIs between modules matter
- How database/schema isolation fits into the design
- When extracting a module into a separate service becomes easier

A modular monolith is still deployed as one application.

But the key difference is that each module has clear ownership, a public contract, and fewer hidden dependencies.

This gives you many of the design benefits of microservices without taking on distributed systems complexity too early.

Check out my courses:
https://www.milanjovanovic.tech/courses

Read my Blog here:
https://www.milanjovanovic.tech/blog

Join my weekly .NET newsletter:
https://www.milanjovanovic.tech

Chapters

## Содержание

### [0:00](https://www.youtube.com/watch?v=S2zqdX411RQ) Segment 1 (00:00 - 05:00)

What is the difference between a monolith and a modular monolith? Does this mean that a regular monolith can't be modular or is there something more to it? Well, in this video we're going to look at the same application implemented as a monolith and a modular monolith. We're going to see what are these benefits that modular monoliths bring and what are the trade-offs that we have to make and some rules introduce to our code base to make this happen. So, let's first talk about modularity. You can think of a module as a self-contained component of a larger system where we combine these modules to produce the overall whole and in the case of a modular monolith, you can see an example here with a payments module, a reviews module, a users module, a bookings module, a disputes module and a notifications module. All of these are self-contained components that provide some functionality to our application and we combine them together to form the complete application that we serve to our users as a single deployment unit. And even though these two applications provide the same functionality, the modular monolith gives us a bit more flexibility because it's comprised of these self-contained modules. And what ability does this give us? Well, first of all, at any point we can take one of these modules and extract it into its own service and this allows us to easily scale it to have different versioning rules or deployment mechanisms for that service. We could opt to use a different database service because of some specific requirements for that module. However, the price that we pay for this is having to do more upfront work than in a regular monolith and if you ever tried to pull out a module, so to say, from a typical monolith and move over to microservices, you probably know what I'm talking about. There are a lot of these hidden dependencies inside of a monolith and you only find when you try to actually increase the modularity of your system. And this is actually what microservices give you. They're basically modules deployed as their own service with a separate physical boundary. What a modular monolith could look like from a practical perspective is still a single deployment unit. However, inside of it, we've got these clearly defined components or modules that implement the actual functionality which we expose through our application. In this example, we're also using separate schemas in the database. This is a nice addition, but it's also not strictly required to have a modular monolith. And then finally, from a module perspective, you can think of them as self-contained applications where, for example, each module uses something like clean architecture or vertical slices. Doesn't really matter since this is an implementation detail, but I think this helps to visualize what a module could represent inside of your code base. Now, in our discussion so far, we are skipping one important detail, and that is how do you actually decide what comprises a module? And I'm going to talk more about this in a moment. So, let's actually go into Visual Studio and take a look at our regular monolith application. I'm going to use a running tracker application that I called Run Tracker for lack of a better name, and I'm implementing it using the clean architecture. So, we've got a distinct domain layer that contains the domain entities. It also has an application layer containing the use cases, and within the domain layer, we see two top-level folders, the users folder and the training folder. And when we dig into the actual classes that we have defined within our domain project, we're going to find that they don't really have a lot of things in common. So, users and followers implement their own related behavior, which is why they are within the same folder, and we can think of this as a small bounded context. And on the other hand, we've got this training folder that has workouts, users, but this only contains an error class, and then activities where the workout is the central type within this feature folder or another small bounded context and a workout doesn't really know anything about the user. The only thing it knows is the user's identifier, which is something that we can allow between different bounded context, but an even better implementation would be having a user type or entity defined for this bounded context that is physically the same user within our system, but logically it's a different abstraction inside of this bounded context. The same applies for the activity, which actually tracks the user's workouts. It does need to know the workout details and how to interact with it, but it doesn't have to know a lot about the user. And from the domain project alone, we can see some nice separation between our two bounded contexts, the users and the training bounded context. However, when we move into the application project, this is where the lines slowly begin to blur. For example, you're going to see feature folders like workouts, users, followers, activities and they all contain use cases that are scoped to these bounded contexts. However, you're also going to see some cross bounded context chatter. For example, in the create workout command handler, we've got a reference to this user type, which comes from the

### [5:00](https://www.youtube.com/watch?v=S2zqdX411RQ&t=300s) Segment 2 (05:00 - 10:00)

users domain and I just alias the namespace here to make this more obvious, but in all the monolith applications that I've worked on, this is how we slowly start to introduce coupling between the entities and bounded contexts in our system. It's not hard to imagine this also expanding to start depending on some user logic. So, for example, let's say if the user has a public profile, then we may want to do some additional work here or based on this being true, we may want to make a call to some other service that we have defined in our system and practically speaking, there's nothing wrong with this, right? It is coupling, but it's also an essential part of building software. An application without any coupling in it probably isn't doing all that much. Another problem with this type of coupling is that it becomes harder to track the side effects of the changes that you make within your system. For example, we may change something in the user bounded context, and it inadvertently introduces a change in the training's bounded context. Another thing that you could see in more legacy projects, or at least projects that have been worked on for a couple of years, is the same lines between our bounded context getting intertwined at the database level. From a practical perspective, there's nothing stopping us from let's say making a join to the users table and just fetching the data that we need to satisfy some query, but this is another form of coupling, except it's now in the database. And database level coupling is notoriously difficult to clean up, and it may make it significantly harder to evolve the system. Now, the other projects in this solution don't really have a lot of interesting things going on in there. It's just technical concerns, implementing things like EF Core, caching, the outbox pattern, storage, where we're using something like S3 to store files, and then we got our web API acting as the composition root and wiring everything up. Let's see how this compares to the same application implemented as a modular monolith. And it could look something like this. Now, what is the key difference here? I introduced a top-level modules folder within the solution, and now each of the modules contains all of the projects that are needed to implement it. So, we have separate domain projects for each of the modules, and they contain the same entities that we had before, but simply having a separate assembly to define our entity gives us a way to prevent other assemblies in different bounded contexts from referencing our entities, which means they are going to need to find another way to access the information that they require. And this is how we can slowly start enforcing the boundaries between the modules inside of our monolith. So, that's the first thing. We want to be very clear where the boundaries of one module begin and end, and we also now have a way to control what is it that we want to expose outside of these boundaries. So, for example, the users module exposes an API project that contains a simple interface definition. And this is now your only coupling point between the training module and the users module. So, if the training module wants to get some information from the users module, the only place it can do that is through the public API. And the public API could be something like an interface, as you can see in this example. It could be some sort of event that gets published from the users module, and we consume it in the training module. So, that depends on if you want to do synchronous or asynchronous communication between the modules. The implementation isn't that important. What is important is that we have a clear public API, and that is the only entry point into the module that we want to interact. So, as you can see, this mostly boils down to controlling the boundaries between the bounded contexts in your system, and also controlling the coupling by owning the public API of the module and deciding what gets exposed and what remains internal within that module. So, in this example here, if the workout module needs to access the users information, it does that through the users API. It doesn't directly reference the user entity and all the information it contains. It only gets a snapshot of the information encapsulated within the user response, and this is sufficient for this use case to provide this functionality. Now, where we can take this further is within the infrastructure projects for each module, I can now define a separate database context, if we were using EF Core, for each of the modules. And this also lets me define a schema where we are going to define the database tables for that module. In the case of the training module, we now have a respective training schema. And other than the logical isolation that this offers at the database level, it also gives you one more important benefit, and that is the ability to control the access policies for each of the schemas. Now, we are getting a bit into some advanced territories here, but I just want you to keep this in mind when thinking about the differences between a monolith and a modular monolith. Also, in the composition root, which is our web API, we're now configuring the dependencies for each of the modules explicitly. So, you can see calls like registering the application layer dependencies for the users module and the training module

### [10:00](https://www.youtube.com/watch?v=S2zqdX411RQ&t=600s) Segment 3 (10:00 - 11:00)

separately. And if we ever want to pull this out, we can just take what's specific to one module, move it into a dedicated service, and it should continue to function the same as before. Now, obviously, we also need to solve things like cross-module communication. If we were using in-memory calls, we would need to implement something that now works across a network. But again, we are going into some more advanced discussions that I wanted to avoid for this video. And going back to one of my original questions, does this mean that a typical monolith cannot be modular? Well, that is absolutely not the case. A monolith can definitely be modular. It's just that the modular monolith caught on as a specific term for a monolith applications where we strictly enforce the boundaries between modules in our code through project-level isolation, through a strict public API, and also database-level isolation. A monolith can be modular. It's just that a modular monolith takes this to a bit higher level and is more strict about controlling the touchpoints and isolation of the individual modules. Well, if you're thinking, "Why should I even care about the modular monolith architecture? " Companies like Shopify that operate on a global scale are using the modular monolith architecture. And I talked about this in this video that you can watch next. If you enjoyed this video, consider gently tapping the like button. Thanks a lot for watching, and until next time, stay awesome.

---
*Источник: https://ekstraktznaniy.ru/video/51636*