Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
Splitting up a Monolith into Microservices is not an easy task. It can be broken apart into multiple steps. Defining boundaries and capabilities are key. Depending on how much coupling you have within your Monolith, and how you couple is a big factor in how challenging this process will be.
Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.
Monolith <> Big Ball of Mud
First, I believe most people have associated the term “Monolith” with a big ball of mud that is unmanageable because of tight coupling. The terms spaghettis code also comes to mind. The reason for this common perspective is likely because almost all codebases with size and age add more and more coupling, which makes things harder to change.
However, this couldn’t be farther from the truth.
A monolith does not need to be hard to change.
A monolith does not need to be tightly coupled.
Large amounts of coupling within a system are because of a lack of boundaries. Coupling is the enemy. However, finding the right balance of cohesion & coupling.
Defining boundaries in any system is key. Microservices are single units that contain a certain set of capabilities within the system.
The same boundaries can and should be applied within a monolith.
The first thing that needs to be done is to define the capabilities of the system. Then defining the boundaries (Bounded Context) which are a collection of those capabilities. If you want to find more info on finding service boundaries, check out my series on Context is King: Finding Service Boundaries
Next to understand the coupling between boundaries.
Which Bounded Context reference/couple to other bounded contexts? Which capabilities in one bounded context need to call a function in another bounded context. Either to perform an action or get data.
If you want to carve off Bounded Context into its own service, you’re going to be stuck with dealing with two forms of coupling.
- Database Schema
Function/api calls that were previously done in-process. Since you’re carving off to a new service, you can no longer make these calls in the same fashion. You now need to make Remote Procedure Calls over the network. This could be done via HTTP, gRPC, etc.
I strongly recommend this be an intermediate step, if at all. I do not believe that synchronous RPC are a good solution for a distributed system.
Ultimately we want each boundary to be autonomous and doing its communication via asynchronous messaging.
However, this can be an intermediate step towards moving portions of a system, in steps, to be independent and autonomous.
All interactions between Bounded Context must be done APIs, which are now done via RPC. Each boundary must own it’s own data. There cannot be a shared database. You need to remove this coupling.
Meaning, you cannot have one Bounded Context query the schema of another Bounded Context.
At this point we’ve created a distributed monolith. You’ve moved all communication that was in-process to an RPC call over the network. This comes with a lot of issues and complexity at the gain of independent codebase and deployment.
You do not want to stay in this position. You want services to be autonomous. To do so, you need to remove the tight coupling of RPC and move towards asynchronous messaging.
Commands and Events will be sent and published to a Message Broker to remove RPC. This is a giant step in gaining autonomy of a service.
If you’re new to messaging, I’ve done a lot of videos about messaging that you can check out on my YouTube channel.
Monolith into Microservices
Once you’ve carved off a Bounded Context, it’s a matter of applying the same concepts to other boundaries and capabilities in your system. Splitting up a monolith into microservices is not easy but can be done.
- Define Boundaries
- Identify coupling
- (Intermediate) Move to RPC
- Define Data Ownership
- Implement Asynchronous Messaging
Loosely Coupled Monolith
I do think creating a Monolith that has well-defined boundaries, communicates via asynchronous messaging is a great place to be as it allows you to carve off boundaries. I have a whole series of blog posts and videos around creating a Loosely Coupled Monolith.