Monoliths, Microservices, and MVCx2
Monoliths! Microservices! Monoliths! Microservices!
Sometimes, you wonder if we are in a bubble, a bubble of opinions. One particular topic that has became the crusade of the year is the debate between monolithic architecture and microservices architecture. Women and men much smarter than us have weighed in, sometimes resulting in quality humor.
After spending some time thinking about the subject and realizing the positives and negatives of my own experience, there is a case for monoliths and microservices throughout the lifespan of a company. Monoliths serve as great foundations, but microservices give you room to grow. Different architectures can help you focus on delivering the best possible experience to the end-user during different points in your company’s life.
This post discusses some of the benefits and drawbacks of both monoliths and microservices. This post also provides a strategy for using both patterns to maximum effect in your young business. Monoliths vs. microservices is not a binary choice. You should understand the pros and cons of both. Design your infrastructure to move between them easily.
Most web companies starting out should build a monolith that can one day be broken apart into a suite of microservices. Let’s explore why and how.
To Monolith
Monoliths are large code bases that often encapsulate an entire business’s operation. If your company has a single Rails code base that powers your site, your internal dashboards, and your user experience, you have a monolith.
Companies like Basecamp and Etsy prefer monoliths within their architecture. The creator of Rails, David Heinemeyer Hansson (@dhh), champions the monolith pattern.
Monolithic architectures have their advantages. They are easy to maintain by the nature of there only being one. There’s only one way to run the tests, one way to deploy, one repo to check out, and only one set of dependencies. This is good if your team has trouble staying organized.
Although many opponents pejoratively use monolith in the same breath as tightly-coupled, that is not always the case. Most languages with robust web frameworks possess enough abstraction functionality to allow for tidy code bases.
Monoliths have their downsides, but most downsides only manifest over time. Build times and test suite runs can creep up into the tens of minutes, sometimes hours. Doing anything significant with the code base begins to feel like molasses. Because test suites are massive and there are so many contributors, your engineers start being held up by silly things like test suite run lengths.
If your team chooses against monoliths because they “don’t scale,” congratulations! You have made a premature optimization. There is no prize. If you however started with a monolith but are worrying about breaking that down into smaller parts, you have a Good Problem.
Well-structured monoliths are great for going from an idea to a 1-year-old company.
To Microservice
Microservices are many smaller independent applications running in harmony to deliver a cohesive user experience. The user should not know they are interacting with several different applications at any given time.
There are a few frameworks that are often reached for when building a microservice, such as Ruby’s Sinatra or its analogues in other languages. Rather than communicating within a single runtime, these microservices often communicate over a transport or application layer protocol such as HTTP, websockets, or something else entirely. The only information shared between services is the message payloads.
Netflix and many other organizations champion the microservices pattern.
Microservices can often be a certain organizational smell. If there are more services than engineers, your organization may suffer from the chronic disease known as engineerus runnus amoukus. This smell is straight pungent if each application has a different level of test coverage, different deploy method, and different dependency considerations. Some argue that microservices can trade code complexity for deployment complexity. Service discovery, unified logging, and robust monitoring can be more difficult to implement in a microservices architecture.
Negatives aside, microservices can be a powerful organizational tool for isolating logic and enforcing organizational discipline. A programmer is much less likely to munge a global state when that is not even an option.
Microservices are also a great way to “peel off” part of an application for performance considerations. Something to peel off early might be an intense, CPU-bound code path like image processing.
Microservices require more discipline to maintain, but can pay dividends over time. In many cases however, they can be a premature optimization.
MVCx2 and the Peel Off
So, were you to start a new application today, what should you do?
Start with two monoliths. One for your server-side and one for the client-side. For your server-side stack, choose your favorite web framework, like Rails, and couple it with a familiar database. The only thing it should expose is set of JSON endpoints, preferably one that speaks JSON API.
On the client-side, choose your favorite front-end framework. You can go with the flavor of the week, or one of the big ones like Angular, Ember, or React. If you are a mobile-first company, this will be your iOS application.
This is MVCx2.
I’d keep you here for hours, but I will instead condense my reasoning into a few short paragraphs. By structuring things this way, you only have a very small overhead price to pay for some structural advantages that will pay dividends. (Provided, of course, your company is around long enough to cash in on the investment.)
This structure gives you a very short path to a public-consumable API, should customers request one. This path also ensures that a native mobile experience shares the same code path as your front-end. Having your server-side application render HTML is an anti-pattern and should be avoided.
On the server-side, you should employ the “peel off” method to slowly spin off microservices. Emphasis on slowly. Your server-side application will likely encapsulate most business logic, and should remain self-contained for quite awhile. Keeping all of this in one place reduces the friction for big decisions and lets your team remain nimble. This is critical in the early stages of a company.
Only when there can be big performance-based wins that have a direct impact on the customer experience should you begin spinning out microservices. By waiting to see where the bottlenecks arise, you can be sure that your abstraction is built in the right place. In a venture-backed startup, this probably will not occur until the 5th or 6th engineer.
Most of all, pick a server-side framework that gives you the flexibility to peel off microservices. Rails is still my go-to for this reason: it may not be the right choice for a project two years down the line, but its nimbleness in the early days is invaluable. Take a look at the Rails::API project to give you just the API engine that you need. With a powerful async library like Sidekiq, it’s easy to start peeling off that code path to run outside of a request and later in an entirely separate service.
The mantra of a company looking to employ MVCx2 should be monolith until it hurts, then microservice.
Conclusion
Encapsulated here are many of the engineering lessons learned throughout the course of LayerVault and Designer News.
Monolith vs. microservices is a false choice. Based on my own experiences and friends’ experiences, a typical web application should start as a monolith or MVCx2. But structure the monolith as if one day it could be broken apart into a suite of microservices. Do not create the Ball of Mud. Know when is enough to begin decoupling. Choose frameworks and technologies that make these transition processes easier.
–
Are you squarely in the monolith or microservice camp? What has worked well for you? I’m curious to hear your experience.
A very special thanks to Erik Bryn, Jordan Hawker, and Ben Holmes for reading drafts of this post.