Think big with microservices: Lego-like software development takes shape
Ever since subroutines appeared in early attempts at computer programming, developers have tried to modularize their code. If only we could treat software as Lego blocks, the reasoning goes, we could mix and match various bits and pieces, building flexible applications by simply snapping their components together.
The notion of web services from the early 2000s was an important milestone in the Lego-ization of enterprise software. Web services were a set of XML-based standards that gave existing software an easily integrable interface—at least in theory. In other words, web services were the bumps on the building blocks—all following the same set of rules, so that regardless of the underlying software, you'd get the benefits of plug-and-play modularity.
However, web services were plagued by a host of issues. Vendors couldn't agree on the specifics, which led to incompatibilities. XML proved too verbose and inflexible. And worst of all, web services suffered from complex spaghetti code with an interface that was every bit as messy as before.
Beyond the buzzword: Reinventing 'service'
Cut to 2015. We are now comfortable with JSON as a more flexible alternative to XML. We understand the principles of cloud architecture, including horizontal scalability, elasticity, and automated recovery from failure. And we're getting up to speed on containers as well. It's time to reinvent the notion of service for this new level of understanding.
Enter microservices. "Microservices" is among the hottest of new buzzwords. But as with so many buzzwords, people struggle to understand the value. Starting with the seminal 2014 article by James Lewis and Martin Fowler, the idea of microservices has taken on a life of its own. And like most overhyped ideas, this one has generated its fair share of confusion as well.
Perhaps the best definition comes from Janakiram MSV, principal at Janakiram & Associates. "Microservices are fine-grained units of execution. They are designed to do one thing very well," according to Janakiram. "They contain everything from the operating system, platform, framework, runtime, and dependencies, packaged as one unit of execution." As a result, "a microservice architecture promotes developing and deploying applications composed of independent, autonomous, modular, self-contained units."
Brick-by-brick, but how 'micro' do you go?
Our Lego block goal for software is finally within reach—and yet, the above definition leaves us with another question: What does it mean for a unit of execution to be fine-grained? The obvious answer is small, as in micro, the prefix that gave microservices their name. But does this notion hold water?
Unfortunately, granularity is a rather general term. Many things can be more or less granular, or finer or coarser-grained. The notion of granularity in the context of services arose in the early 2000s in the context of web services. However, since web services were interfaces rather than software components, granularity referred to the granularity of the interface.
In other words, a fine-grained service sent and/or received a small number of values (say, a single number or string), while a coarse-grained service sent and/or received structured information that contained several values (for example, an XML document), as shown in the diagram below.
Determining the appropriate granularity for a service interface was tricky, as there were pros and cons for any level of granularity. Fine-grained interfaces generally lacked business context, but tended to be more reusable. Coarse-grained interfaces, on the other hand, often had a clear business context, but were typically purpose-built for a particular situation, which limited their reusability.
Right-sizing microservices is key
Microservices, by contrast, are more than interfaces. As Janakiram points out, they are the whole package—a unit of execution including code, runtime, and more.
In his new book, Building Microservices: Designing Fine-Grained Systems, Sam Newman says, "The question I am often asked is, 'How small is small?' Giving a number of lines of code is problematic." This is due to a variety of factors, including language differences and the specifics of the task at hand.
He offers a practical measure. "If the codebase is too big to be managed by a small team, looking to break it down is very sensible." But he adds a caveat: "The smaller the service, the more you maximize the benefits and downsides of microservice architecture."
In other words, make your microservices too small and you'll have to manage excessively large numbers of them, but make them too big and you'll lose the benefits that drove you to create them in the first place.
Newman makes one other comment on the subject: "Another somewhat trite answer I can give is small enough and no smaller." This point underscores an important principle of microservice construction: parsimony.
A parsimonious microservice is as small as it should be and, as Newman says, no smaller. In other words, during your iterative refactoring efforts (part of any agile approach), revisit your microservices and see if there's anything extra in them, or if splitting up a microservice into two or more microservices improves matters. If so, continue to pare them down and split them up until such efforts no longer move your project forward.
Get cohesive: When microservices belong together
Another important microservices principle—and in fact, a principle of modular software design since the 1960s—is cohesion. A microservice is highly cohesive if its elements belong together. In other words, it does one thing and does it well, as shown in the diagram below.
Web services frequently suffered from low cohesion. Sometimes WSDL files contained dozens or even hundreds of operations, where a single service did a wide range of different tasks. Reacting to the problems with that kind of service is one of the primary motivations for microservices.
Therefore, while parsimony and cohesion make more sense in the context of microservices, granularity makes more sense in reference to interfaces—and the concepts are quite different. After all, a well-built microservice might (and typically would) have a coarse-grained interface, for example, if it accepted and/or returned a JSON document.
However, as the title of Newman's book suggests, an entire system built with microservices would itself be fine-grained: yet another sense of the notion of granularity. A collection of microservices would be fine-grained if they tended to be parsimonious and highly cohesive, regardless of how big they were, and even though their interfaces should be coarse-grained.
Thinking big with microservices
Don't be misled by the "micro" prefix. While it's true that microservices should be small in some sense of the word, that principle should not lead you to build microservices unsuited to your needs.
Remember: If the only kind of Lego block you use is the tiny, one-bump version, you won't be able to build anything interesting. But you don't want massive blocks either. In reality, you need a range of different sizes, and many of them will be small enough, but not too small. The same is true for microservices.