There’s a lot of information out there about microservices. In this post, I’m going to describe how I see people approaching microservices and point out some of the good bits of information to get familiar with the concepts. Fair warning, this is somewhat of a meta-post.
Two Common Approaches
SOA: A great starting point
After reading a few articles on microservices, people that have been creating services or have read about services for the last few years inevitably compare microservices to Service Oriented Architecture (SOA). Apart from the silly comparison that “Service Oriented Architecture” has 27 letters and a 3 letter acronym that can be pronounced (unless you’re an initialism person) and “microservices,” while being 13 letters, has a 2 letter abbreviation “μs,” but is pronounced the same with no added benefit to shortening - a sort of architectural irony - there’re a lot of good parallels with microservices and SOA.
Approaching microservices via SOA is a great starting point since many in the software industry are familiar with SOA. Some of us greybeards will get a bit prickly and bring up things such as CORBA, etc. - and they’re right: the name may be new, but the concepts have been around for a good long time. One of the first instances of the term microservices is in discussions of the linux microkernel (add reference here). Further, microservices are almost always implemented in a unix-style of composition, small singly focused services, used together.
Most of us in the industry actively acknowledge that vendor-driven SOA has hit it’s plateau of usefulness, teetering on being considered harmful - damaging the principles that SOA stands for (unless, of course, you work for an ESB vendor) and of software architectures based upon SOA.
Classic SOA principles still apply to microservices such as self-contained, loosely coupled, reusable, business-oriented services that communicate via a (typically) network protocol, but SOA has baggage. That baggage is primarily due to the vendor-driven implementations of SOA applications, such as ESBs, and protocols, such as SOAP and the WS-* suite, which have engendered monolithic applications, nominally are made up of services. Consider microservices as a new approach to implementing SOA, mixing in concepts of distributed computing and lean, and you’ll have the lineage. A great analogy is also the relationship between agile, and an implementation of agile, like scrum or kanban or XP.
Microservices is SOA principles done right.
Implementations: Blind groping at elephants
The other day, one of my friends IM’d me asking if I’d heard of CQRS because some of his colleagues were super excited and going to implement it. Apparently, there’s a .NET article or two espousing the wonders of Command Query Responsibility Segregation and other developers on his team were holding a meeting to discuss using it and he wanted to know my opinions on it. I’ll talk a bit about CQRS and other patterns, later, but this entré to microservices, especially for existing applications, a bit like randomly picking a page out of the Gang of Four _Design Patterns_ book and refactoring everything based upon that.
Another - and better - approach that people take towards grasping microservices is via containers, like Docker, or practices like devops. Knowing about containerization implies that the person or organization values some level of immutability in deploying applications as services and has some buy-in for this style of development and deployment. Devops ups that buy-in with small teams focused on producing products and owning the full lifecycle of the product. The icing on this perspective on microservices is that it’s a natural exension of the trends of agile->devops->continuous delivery.
A colleague of mine, Ian Goldsmith, drew this diagram to represent this conception:
Venn Diagram of Microservices
The sources: a brief review
One of the seminal articles on microservices is Fowler and Lewis’s Microservices which espouses microservices as an architectural style and provides 9 principles of microservices. I’ll go over these shortly, but one thing to highlight from Fowler & Lewis’s article is that they make clear that the principles aren’t criteria for conformance. Another important work is Sam Newman’s Building Microservices where he provides a substantial amount of discussion around the 7 principles he’s encountered.
You might note that there’s no authoritative set of principles, patterns, or even implementations - even though there are many principles, patterns, and implementations. Microservices is an emerging and evolving field of software architecture with a solid body of discussion and active practitioners which are providing this knowledge.
Adrian Cockcroft of Battery Ventures, previously of Netflix - one of the innovators in the microservices field - describes microservices like this: “Loosely coupled service oriented architecture with bounded contexts.”
Sam Newman of ThoughtWorks and another microservices expert, says this about microservices: “Small autonomous services that work together.”
Working through reading articles on microservices will give a more holistic approach about where and why microservices arose and when and how to apply microservices architecture principles.
Before we move into a deeper inspection of the sources - are those two common approaches to microservices “wrong?” Or, asked another way - again by a friend of mine while I was droning on about this subject, paraphrased - why am I such a hater?
To be clear, I’m not a microservices hater and I’m not really a hater on anyone who approaches microservices those ways, it’s just that they’re incomplete. Glomming on blindly to the latest fad in software development is not a great thing - eventually, the way software moves, something at the edges will become more mainstream and better understood. In the case of the intrepid CQRS adopters, they’ll eventually figure out that cherry picking a specific feature of microservices won’t yeild the magical results they might be expecting.
A historical SOA approach is reasonable, too, but software people by-and-large are pragmatic people and a “history of software” lesson tends to be less instructive than just getting in and doing it.
The insight I got from reading about and deploying microservices is as follows: Implementing microservices - whether with an existing application or green field - requires some prerequisites, without which, the objective of scale isn’t being achieved. Some of these prerequisites are much easier to understand than others, primarily due to our human nature of trying to analogize with past concepts.
Devops is a clear one - being able to have an agile, cross-talent team deploy what they build - but it’s not enough. Having an organization on the way to or at achieving continuous delivery is a prerequsite. Being able to deploy in a discrete manner that automates testing and load along the way is an advanced organizational behavior. Not everyone’s there yet, not everyone’s at devops, and not everyone needs that level of intricacy to behave this way to deploy software.
Decentralized everything is another. While that sounds vague, because it is, it implies no “layered” architecture. No abstracting security to a security infrastructure, no abstracting orchestration to some orchestration gateway, no abstracting of governance to a governing body. This one’s also difficult to achieve for existing organizations that built around these processes of separated architectural and organizational layers. Why’s this important, though? Well, to move at scale, microservices dictates that the unit of a service be fully secured, governed, and managed by the “two-pizza” team. For green field microservices project, this isn’t too hard a concept. Refactoring an existing organization can be.
A corallary to the above is domain-driven design for data - decentralizing the data, creating bounded contexts, and therefore relying on some level of eventual consistency in the data which services access. Additionally, dealing with failure - whether network, self, or data (inconsistencies) is another overlooked aspect. This is where one of the parents of microservices - distributed computing - rears it’s head. Microservices puts a burden on the service design team to deal with the data for the service and the service alone as well as fault tolerance behaviors. No calling out to anything else to get this done, resulting in additional code. Shared libraries can help here, but the onus is on the service developers. In larger organizations, “boilerplate” gets abstracted (for example, in the case of security) to another “layer” or even another group.
Scale occurs at every level - Organization, systems, devices, development, testing. And not everyone needs to scale this way.
Microservices is SOA at scale.
Mentioned above, the disillusion of SOA when used with “vendor-driven SOA” products provided a great impetus for practitioners to go back to SOA’s first principles and remix them with best practices of modern software development. SOA, at its roots, is not about implementation but of organization, of the architecture of a system. Scaling a system has been the greatest priority of the “internet 2.0” or “web scale” era - taking the “move fast and break things” mantra of Facebook to heart and making it stable.
To be very clear here, scale is one of the prime drivers. The goal isn’t speed of invocation, it’s throughput - how many requests can be handled, elastically, rather than a finite amount quickly.
Also frequently cited - so much so, that the in-joke is that you can’t have a microservices article without mentioning it - is Conway’s Law, from Melvin Conway (inventor of the Game of Life), from 1968:
organizations which design systems are constrained to produce systems which are copies of the communication structures of these organizations
This implies that the methods of communications between groups - say your project management, dev, qa, and ops groups - defines the process by which your system will be built. There’s your nice, flawed waterfall model of system design and development.
Recasting SOA in a modern light has meant that practitioners have had to discover the gaps in SOA and accommodate for them. This has led to the principles in Fowler and Lewis’s article, mentioned above:
- Componentization via services - a SOA-originating concept, with the modern concept of adhering to published interfaces and being very careful when changing those interfaces *Organized around business capabilities - SOA principles of focusing on business features, and also Uncle Bob Martin’s single responsibility principle
- Products not projects - Cue Conway’s Law reference here, but also the “you build it, you run it” aspects of devops. Note that a monolithic application could do this, too, but when things are so related, there tends to be heavy context leakage
- Smart endpoints, dumb pipes - lightweight communications with no central communications; choreography vs. orchestrations, no ESBs
- Decentralized Governance - Each microservice knows what to do for itself and aggregate microservices interact in a unix pipe-and-filter way
- Infrastructure automation - microservices team controls deployment, further, there’s an infrastructure that’s necessary for microservices - whether it’s a registry, health dashboards, etc. those have to exist for microservices to thrive (and are probably microservices, themselves)
- Design for failure - services, not some external thing, must accomodate for failure; plan for it, code for it, report failure Evolutionary design - microservices will evolve; granularity of microservices will change
As with any architecture choice, there are tradeoffs. For microservices, I’ve compiled a few, here:
- Provisioning of individual services puts a burden on operations. This is typically mitigated/accepted (with relish!) via devops
- Communications between microservices isn’t defined - including routing, integration and discovery - thereby putting the burden on clients. Concerns from vendor-driven SOA / ESB vendors show up here, too: There’s no global entity (like an ESB). Clients will typically handle failure and control their own state, rather than abstracting these functions and delegating them to a global controller.
- Remote calls are more expensive than in-process (single monolith app) calls and remote APIs become more coarse grained. This tradeoff is accepted because the boundaries between services are much more defined, allowing for scaling.
- Courser-grained APIs may mean more calls between services. Again, this trade off is accepted in order to provide scalabiliyt as it’s easier to scale out and down than doing the same with monloithic apps.
- Centralization of choreography puts a burden on microservice designers to understand that their service may beused in a pipe & filter pattern. This requires microservice designers to build an API for their service that can be used in this fashion and to keep that API relatively stable.
- Fragility is introduced by having multiple, granular services. Compensation via resliency patterns is the typical answer to this tradeoff.
- Service dependencies through HTTP. Since many microservices prefer the REST/HTTP communications paradigm, although at extreme scale a serialized RPC mechanism (protobuffers, Thrift) may be used, some see HTTP as a non-efficient mechanism. I’m not sure harping on HTTP is really a concern. There’s HTTP/2 + protobuffers (et al.) that provides great mechanisms for communication. Further, scalability benefits are preferred rather than transparency in communication (such as “human readable” XML).
- Multiple datasources for decentralized management put a burden on data managers to understand Bounded Context design pattern; no single logical database; updates become more complex; eventual consistency
- Increased monitoring with the assumption to fix microservice connectivity problems puts an additional burden on operations as well as architecture design. Lots of moving parts means that monitoring is essential - this is an additional infrastructure / system burden.
- SLA is only as good as the weakest link - resiliency patterns, contract-based interaction help here.