---
title: 'I Built the Same App as a Monolith and a Modular Monolith'
source: 'https://youtube.com/watch?v=S2zqdX411RQ'
video_id: 'S2zqdX411RQ'
date: 2026-08-08
duration_sec: 683
---

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

> Source: [I Built the Same App as a Monolith and a Modular Monolith](https://youtube.com/watch?v=S2zqdX411RQ)

## Summary

This video compares a monolith and a modular monolith by implementing the same application in both styles. It explains the benefits of modularity, such as easier extraction of modules into services, and the trade-offs, including more upfront work and the need to enforce boundaries. The presenter demonstrates the differences in code structure, database schemas, and dependency configuration, concluding that a monolith can be modular but a modular monolith enforces stricter boundaries.

### Key Points

- **Introduction to Modularity** [00:01] — A module is a self-contained component of a larger system. A modular monolith combines these modules into a single deployment unit, with examples like payments, reviews, users, bookings, disputes, and notifications.
- **Benefits of Modular Monolith** [01:13] — Modules can be extracted into separate services for scaling, versioning, or deployment flexibility. However, this requires more upfront work and reveals hidden dependencies when extracting modules.
- **Practical Structure of Modular Monolith** [02:08] — A modular monolith is still a single deployment unit but has clearly defined components. It uses separate database schemas and each module can follow clean architecture or vertical slices.
- **Regular Monolith Example** [03:05] — The presenter shows a Run Tracker app using clean architecture with domain and application layers. It has bounded contexts like users and training, but cross-context references introduce coupling.
- **Coupling in Regular Monolith** [04:41] — Cross-boundary chatter occurs, e.g., the create workout command handler references the user type from the users domain. This coupling can lead to unintended changes and database-level joins.
- **Modular Monolith Structure** [06:57] — A top-level modules folder contains all projects per module. Separate domain projects prevent cross-boundary references, enforcing boundaries. Each module exposes a public API as the only entry point.
- **Database Isolation** [09:09] — Each module can have its own database context and schema, allowing logical isolation and control over access policies. This is an advanced benefit.
- **Monolith vs Modular Monolith** [10:26] — A monolith can be modular, but a modular monolith strictly enforces boundaries through project-level isolation, public APIs, and database isolation. Companies like Shopify use this architecture.

### Conclusion

The modular monolith offers a middle ground between a monolith and microservices, providing flexibility and maintainability through enforced boundaries. It's a viable architecture for large-scale applications, as demonstrated by companies like Shopify.

## Transcript

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 that we have to 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 to be paid 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, you only find when you try to actually increase the modularity of your system. And this is actually what microservices give you.
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 module among them. 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. It 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 I going to talk more about this in a moment so let 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 RunTracker, 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 you 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 contexts. but an even better implementation would be having a user type or nd 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 future 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, you've got
reference to this user type which comes from the user's domain and I just added a namespace here to make this more obvious. But in all the monolith applications that I ever 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 POSIT 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 nothing wrong with this right It is coupling but it also an essential part of building software An application without any coupling in it probably isn 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 contexts getting intertwined at the database level. From a practical perspective, there is 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 the score caching, the outbox pattern, storage where we're using something like S3, two store files, and then we've got our web API acting as the composition group 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 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 our 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 it is 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 modules
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 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 user's information, it does that through the user's API.
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 its functionality.
Now where we can take this further is within the infrastructure project 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 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're 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 group, 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 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 the 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 application where we strictly enforce the boundaries between modules in our code through project-level isolation, through a strict public API, and also database-level
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!
