[00:01] learn more about software architecture, you can actually learn a lot from Big Tech. So, in this video, we're going to take a look at Shopify's modular monolith and see what insights from it we can apply to our .NET applications. [00:15] Shopify is one of the biggest e-commerce platforms in the world, and it might come as a surprise that the core of their system actually runs as a modular monolith. Now, it wasn't always a modular monolith. It started as a [00:27] regular layered monolith application, but we're going to discuss how they landed on a modular monolith and what they did to improve their architecture. Now, to give you some sense of the scale that Shopify operates at, they have [00:39] nearly 3 million lines of code in their code base. In last year's Black Friday sale, which is a good benchmark of the scale that they operate on, they handled more than 6 billion in sales, where the number of requests per minute peaked at [00:53] around 500 million. Also, at peak, they operated at more than 50 million requests per second on their databases. So, I'd say this is fairly impressive, and I think we would all like to work on a product that operates at this scale. [01:06] are using the modular monolith architecture to run their core platform. Now, how did Shopify actually start? It was built as an e-commerce store, and it used the classic Rails monolith, which uses the MVC architecture. MVC is short [01:22] for Model View Controller, and actually it's what ASP.NET Core applications used to implement for a very long time. This was the architecture in .NET Framework, and you can also build MVC applications that use controllers and views in .NET [01:35] 10, which is currently the latest version. Now, the problem that Shopify encountered with their Rails monolith application is that all of the code for their domain was tightly intertwined, which just mean everything was jumbled [01:47] together over the years as the platform was getting developed, and it looks like was causing them to slow down. The example they gave is concerns like billing, shipping, checkout, and orders not having a very clear separation in [02:01] the code base, and this is a symptom of not enforcing clear boundaries inside of your code, and it can really come back to bite you when you want to evolve your product in the future. Now, obviously, Shopify became a big success and a good [02:15] product, and their monolith architecture served them well. It worked great until it didn't. I have this theory that all monolith systems are either going to become what some would call a majestic monolith or just a big ball of mud. Now, [02:29] what's symptomatic of a so-called big ball of mud is a lot of coupling between components without any clear boundaries enforced between them. Now, coupling itself isn't really a problem. A system that has no coupling probably isn't [02:42] doing all that much. What we are actually after when architecting our solutions is controlling the coupling to some extent, but the bigger problem here is having no clear boundaries and a high degree of coupling, which means that [02:56] whenever you make a change to, let's say, the tax calculation component, you inadvertently cause a bug or a breaking change within the shipping component, for example. And in large systems, that Shopify definitely is, side effects like [03:10] these that show up in coupled components can be pretty problematic to solve, and they slow you down and reduce the rate that you can ship new features at. So, a into, I already mentioned this, and the [03:23] first one is fragile code. So, for example, changing the tax logic will break the shipping logic or tests. Another pain point is that everything is there's nothing preventing one domain from calling another. A side effect of [03:36] this is a higher learning curve when you want to onboard a new teammate onto your system, they would have to learn many moving parts in order to be productive. boundaries enforced inside of the code base. And another pain point that they [03:51] had is they had a large number of tests, more than 100,000 of them. Now, a small digression, having this number of tests in your code base, assuming they are high quality tests, is definitely a good thing to have. Now, why was this a [04:03] problem in their case? Well, it was slow to execute, and this was something that out a survey to their developers, and they came to a consensus that the architecture which they had in place at the time was causing friction. So, what [04:16] speaking, they had three options. The first option was doing nothing and just staying with the existing monolith. The second option, which is the one they chose, was to migrate to a modular monolith. Now, they still kept them with [04:30] a single deployment unit, but they got the strong boundaries that you get with I'll speak more on how we can achieve this in a moment. The option which they considered, but did not choose, was microservices, and this would also give [04:45] them strong boundaries. However, it would come into a cost of multiple overhead. And these were trade-offs that they didn't want to accept, so they went with a modular monolith. Now, I do believe this entire migration happened a [04:58] couple of years ago, and it's not to say that they don't have dedicated services, which we could call microservices. However, their core system is a modular monolith. Before we discuss what a modular monolith is, let's spend a [05:10] moment discussing why you would not choose microservices. If you care about microservices means that you need to maintain multiple deployment pipelines, one for each of your services. Then, there is the network latency overhead, [05:25] cross-service call. Another common aspect of microservices is that they usually come with their own database, which is isolated from all the others. domains. Now, most of the time this is a good thing, as it also enforces a [05:40] boundary at the database level, which is a strict level of data separation. However, it begs the question, what do I do when I need this data and this other service owns it? Another pain point of microservices is that large-scale [05:54] refactors are quite risky, and they require coordinated multi-service deploys to get right. This is a much bigger undertaking than when you're a monolith. And of course, because you [06:07] you need to figure out how you're going to solve API versioning for each of the singular API version for all of your services, or are you going to version [06:19] each service independently, which causes version drift across your API, and it you're going to have to think about. And in recent years, we've actually seen more than one example of Big Tech backing away from microservices as their [06:33] default choice and being more open to embracing the monolith architecture. Now, what is a modular monolith? Here's an example of the same application implemented using microservices, a modular monolith, and your Big Ball of [06:46] Mud monolith, which is all too common. So, in microservices, you get these physically enforced boundaries, where each bounded context can be deployed as a separate service, and the communication between them is usually a [06:58] network call with something like REST over HTTP or gRPC. You could also use events and messaging, but the main takeaway is that the communication between them usually involves some sort of network call. Now, in contrast, a [07:12] modular monolith can enforce the same logical boundaries while maintaining a single deployment unit, and we enforce these boundaries by having each module expose a public API which the other modules can call into. Now, in a Big [07:26] Ball of Mud monolith, you've got all of your code for all of the bounded context without a clear boundary between them. And one more way I like to think about the modular monolith is simply a monolith done right. Let me give you one [07:39] more comparison, where we've got the number of deployment units on the horizontal or x-axis and modularity on the vertical or y-axis. Our overall goal is to improve the modularity of our system, which means that we can clearly [07:53] decompose it into a set of dedicated components that have high cohesion, and they offer some functionality inside of our system. We typically expose this functionality as, let's say, API endpoints, which our client applications [08:05] can call to trigger the desired behavior. Now, in a system with high modularity, we're going to have a number of these modular, well-isolated components with clearly defined cohesive behavior come together to form the [08:19] complete system. And the benefit of modularity is being able to, again, control coupling, as it becomes easier to figure out what depends on what. And also, when making changes to the components inside of our system, we've [08:32] got a better way to control the impact of changes made inside of one component and the side effects that are caused within other components inside of our system, which is also the reason why microservices became so popular, because [08:45] they give you a very easy way to enforce the boundaries within your system. Each microservice is its own boundary and is enforced by the very nature of the deployment architecture, where each microservice is a single deployment [08:58] unit. Now, we can achieve the same level of modularity also in a monolith with a single deployment unit, and a nice way I like to think about this is that microservices elevate the logical boundaries that we have in place inside [09:12] of the modular monolith into physical boundaries when we move into separate deployment units. So, to give you a brief recap of what a modular monolith is, it's one deployment unit with strict internal boundaries between the modules, [09:26] and we've also got no network overhead. So, Shopify started as a monolith, but they decided to migrate to a modular monolith. And the first stage of this journey was called componentization, which started as a very simple idea. [09:40] Here on the right, you can see their actual starting point, and this was from blog, and you can see a very similar folder structure with typical MVC architectures that we can see out there in the .NET space. So, you've got [09:54] folders like assets, channels, controllers, jobs, mailers, models, views, and so on. And it's very obvious that we are grouping components together based on their technical capability. So, all controllers go inside of one folder, [10:07] and all models go inside of one folder, and all data access goes inside of one folder. And this leads to a system with very low cohesion and no clear components. So, within the componentization process, they move to a [10:21] structure where they organize their system based on the business domain. So, now you've got a components folder, and inside of it you've got a dedicated business domain. So, apps, billing, checkouts, taxes, things like orders and [10:33] shipping probably be their own components. And all they did within the componentization effort was just refactor their folder structure to improve the cohesion of their system and to group together related types. How did [10:45] they do this? Well, they first listed out the 6,000 or so classes that they had in their system, and then they manually labeled each one with stakeholder input to figure out which domain it falls into. Then they just [10:58] wrote an automated script to move all of the files into the respective components folder, and luckily they still had their full test suite to validate that this worked as expected. So, this was their first step. They just reorganized their [11:10] structure to group files together based on their business domain. The next step was actually enforcing some boundaries between these components. So, what they did is they exposed a public API for each component, what I like to call a [11:24] module. And you can think of a public API as a simple interface in C#, that exposes the functionality that's publicly available from that component or module. What's important is that you keep the implementation of the public [11:39] API internal to the module, and we can do this with the internal keyword in C#, as well as separating each component or module into its own C# project. And then you've got complete encapsulation, where the implementation is internal to the [11:53] project, and we can expose the public API as a public interface. So, now each component within their modular monolith exposed a public API that other components could call into, and suddenly boundaries start appearing inside of [12:06] your code base, and you've got something to enforce those boundaries. Without a clearly defined public API, any component can call into the internal implementation details of other components. However, with the [12:19] implementation details kept internal and a public API exposed, you've suddenly got a good way to enforce those boundaries. You can take this a level further by enforcing the boundaries at the database level. I've actually got a [12:32] video about that, which is going to pop up in the corner right here, and I'm description, where I show you how you can iteratively improve how much you're final level being enforcing the boundaries at the database with separate [12:48] schemas for each component, where the tables for each schema live. And then you introduce a dedicated user for each schema, and only the module where that schema belongs to can access those tables. Now, optionally, you can grant [13:01] extra permissions and expose some views, which other modules can call into, although I still prefer to do this through the public API and my C# code. So, the overarching idea in their migration to a modular monolith is [13:15] controlling coupling and improving the cohesion inside of your system. Now, what you're actually after when designing a code base is change locality. to code that changes together to also live together, which is also why [13:28] something like vertical slice architecture is very popular, because it enforces this principle by design. You don't want to group by data or technical capabilities. Instead, you want to group by task or functional capabilities or [13:43] functional cohesion. As I said, vertical slice architecture does this well, but because if you've read the book, you know that the core idea is the direction of dependencies, yes, but also organizing the system around use cases, [13:58] which are actually there to improve the cohesion of your system and to help you group your code by the functional capability that it offers. And if you're already implementing a modular monolith, chances are you won't get it right on [14:11] symptomatic when I was first getting started with modular monoliths is when you have to make a small feature change, but it somehow affects multiple modules within your system. And this is actually a clear sign that you've got your [14:24] boundaries implemented wrong. However, the good thing is you're working in a monolith. So, reorganizing your boundaries is a matter of refactoring, and you still only have one deployment unit. And here's an example of what a [14:36] modular monolith could look like in .NET with a bunch of additional moving parts that you would typically see, like an identity provider, a distributed cache, an API gateway to act as the single point of entry into the system. However, [14:49] at the core, it's still just a single monolith with clearly defined boundaries between the modules inside of the modular monolith, each of them exposing a public API that the other modules can [15:01] call. And additionally, in this case, I'm also enforcing the logical separation of the data for each module by having it live inside of a dedicated schema in the database. And yes, I know I have a typo at schema, but honestly, [15:15] I've been using this picture for so long that I would feel bad correcting this. And if this architecture diagram looks interesting, and you would like to see how I built this as a modular monolith completely from scratch, you can take a [15:28] look at the pinned comment below to learn more about the modular monolith architecture. I'm also going to leave a sample code base that you can get for free in the description of this video. And if you want to learn more about [15:40] modular monoliths, I highly recommend taking a look at this video next. If you enjoyed this video about Shopify's modular monolith, I ask you to gently tap the like button. Thanks a lot for watching, and until next time, stay [15:54] watching, and until next time, stay awesome.