When starting off with microservices, it's hard for teams to know what exactly constitutes a well-designed microservice. Several teams we've spoken with have fallen into the same traps of making their microservices too small or too tightly coupled. This article distills the wisdom of interviews with dozens of CTOs into five characteristics of well-designed microservices, provides additional guidance for larger teams, and highlights pitfalls to avoid when determining service boundaries.
The Difficulty of Drawing Boundaries
In the early days of SparkPost Chris McFadden, VP of Engineering at SparkPost, and his team had to solve a problem that every SaaS business has: they needed to provide basic services like authentication, account management, and billing.
The core problem, of course, wasn't how to charge their users money. It was how to design their user account microservices to support everything that goes along with that problem domain: user accounts, API keys, authentication, business accounts, billing, etc.
To tackle this, they created two microservices: a Users API and an Accounts API. The Users API would handle user accounts, API keys, authentication and the Accounts API would handle all of the billing related logic. A very logical separation, but before long, they spotted a problem.
"We had one service that was called the User API, and we had another one called the Account API. But the problem was that they were actually having several calls back and forth between them. So you would do something in accounts and have call and endpoint in users or vice versa," he continued.
The two services were too tightly coupled.
"We realized that in most cases, you really don't want to have one service calling another service in a sometimes circular way. That's generally not a good idea," Chris explained.
The question is, how do we avoid these microservice design pitfalls and what patterns should we look for?
First, Do No Harm
When designing and creating a microservice, don't fall into the trap of using arbitrary rules. If you read enough advice, you'll come across some of the rules below. While appealing, these are not proper ways to determine boundaries for microservices.
Arbitrary Rule 1: A microservice should have "x" lines of code
Let's get one thing straight; there are no limitations on how many lines of code there are in a microservice. A microservice doesn't suddenly become a monolith just because you write a few lines of extra code.
Arbitrary Rule 2: Turn each function into a microservice
If a function that computes something based on three input values, and returns a result, is that a good candidate for a microservice? Should it be a separately deployable application of its own? This really depends on what the function is and how it serves to the entire system.
Arbitrary Rule 3: Implement a "rule" without respect to your context
Bear in mind that these and other arbitrary rules given in a vacuum may not apply to your situation at all, and could actually make very little sense for your team. These rules include those that don't take into account your entire context such as the team's experience, DevOps capacity, what the service is doing and availability needs of the data.
Characteristics of a Well-Designed Microservice
After avoiding arbitrary rules, it's natural to wonder how to go about designing microservice boundaries. If you've read about microservices, you've no doubt come across advice on what makes a well-designed service. Simply put: high cohesion and loose coupling. while sound advice, these concepts are quite abstract.
Instead of abstract advice, successful CTOs discussed design principles that helped them create thoughtful microservice boundaries.
Characteristic 1: A well-designed service doesn't share database tables with another service
As we saw in Chris McFadden's case mentioned earlier, if you have multiple services referencing the same table, that's a red flag as it likely means your DB is a source of coupling.
It is really about how the service relates to the data, which is exactly what Oleksiy Kovrin, Head of Swiftype SRE, Elastic, told me:
"One of the main foundational principles we use when developing new services is that they should not cross database boundaries. Each service should rely on its own set of underlying data stores. This allows us to centralize access controls, audit logging, caching logic, et cetera," he said.
Kovyrin went on to explain that if a subset of your database tables, "have no or very little connections to the rest of the dataset, it is a strong signal that component could be isolated into a separate API or a separate service."
Darby Frey, Co-founder of Lead Honestly, echoed this sentiment by telling me that, "each service should have its own tables [and] should never share database tables."
Characteristic 2: A well-designed service has a minimal amount of database tables
The ideal size of a microservice is to be small enough, but no smaller. And the same goes for the number of database tables per service.
Steven Czerwinski, Head of Engineering, Scaylr, explained to me during an interview that the sweet spot for Scaylr is, "one or two database tables for a service."
"We have a suppression microservice, and it handles, keeps track of, millions and billions of entries around suppressions but it's all very focused just around suppression so there's really only one or two tables there. The same goes for other services like webhooks" explained Chris McFadden.
Characteristic 3: A well-designed service is thoughtfully stateful or stateless
When designing your microservice, you need to ask yourself whether it requires access to a database or is it going to be a stateless service processing terabytes of data like emails or logs. Be clear about this upfront and it will lead to a better designed service.
Characteristic 4: A well-designed service's data availability needs are accounted for
When designing a microservice, you need to keep in mind what services will rely on this new service and what's the system-wide impact if that data becomes unavailable. Taking that into account allows you properly design data backup and recovery systems for this service.