Yesterday, I listened to episode 1,856 of the dotnet rocks podcast with guest Layla Porter, Developer Advocate at VMWare. The show discussed "Modular Monoliths"; and, was one of those exciting moments in which I learned that the mental model I've constructed about an idea is completely wrong. That is to say, a "modular monolith" is not at all what I thought it was.
When I first heard the term "modular monolith", I assumed that it simply meant a monolithic codebase with well-defined boundaries. And, more to the point, I assumed that those boundaries were drawn around domain models. As such, I figured that a modular monolith would have separate modules for "Users" and "Products" and "Invoices" and "Preferences" and so on.
To be clear, I had no reason to assume any of this - it's just how my brain decided to fill-in my gaps in understanding.
On the podcast, Layla did not discuss modularity in terms of "features" or "domain models" (the way I had initially considered it). Instead, she discussed it in terms of "synchronicity": which areas of the application had to block-and-wait in order to produce the response for the user; and, which areas of the application could take place asynchronously without creating a poor user experience (UX).
The way I interpreted this is that everything required to construct the user response should be in the same module. That is, everything with a synchronous dependency should be grouped together. The only code that can reside in "other modules" is the code that can be executed in the background at a later time.
ASIDE: Layla also advocated for using some form of "messaging" to communicate between modules, even they are located within the same monolith.
When I reflect back on previous conversations about "modular monoliths", this new understanding makes so much more sense. Consider the concept of evolving a modular monolith into a distributed microservices architecture: If different modules were synchronously dependent, breaking them apart would lead to synchronously dependent microservices. Which — we now know after years of fumbling through distributed systems — is really just a distributed monolith.
Which is, of course, the worst of all possible outcomes: all the complexity of a distributed system architecture combined with all the complexity of a monolith.
If, on the other hand, monolithic module boundaries are draw around synchronicity constraints, then splitting a modular monolith into a distributed services architecture would have no real bearing on how the application operated. Asynchronous communication would remain asynchronous; and, the user's experience would never degrade (since all synchronous dependencies were still collocated within the same module / service).
This isn't the first time I've been totally misguided by my own assumptions. And, I'm sure it won't be my last. But, at least I now have a more clear understanding of what a modular monolith is. Which, in turn, helps me better understand what a beneficial microservices architecture could look like. Thanks Layla Porter!
Epilogue on Choosing Microservices
On the podcast, Layla talked about helping people understand whether or not a microservices architecture would make sense for them. She summed it up as follows:
If you can't answer YES right away, then the answer is No.
I like it!