What, Why, When: Microservices
A Straight-Up Guide
Microservice is a style of architecture that splits applications into remotely accessible components, and “microservice” also refers to an instance of such a component.
Simple Idea, Seems Complex
Microservice architecture is one of those ideas that is actually simple but can spiral into complexity as soon as you open your mouth to explain it.
We’re going to avoid that here. This is a straight-up guide, designed to cleave to the essential of the thing.
If I were going to put a name on the idea, I might call it “remotely decoupled application architecture”.
We’re going to cut through to the essence of the what, the why and the when you should use microservices.
The first key to avoiding confusion is to first ensure you know exactly what a service (or web service) is in the first place. A service is any remotely available resource that can respond to requests.
For all intents and purposes, this means services provide the mechanisms that make URLs work. The entirety of the web (aka the cloud) can be seen as a collection of interacting URL service providers (servers) and consumers (clients).
We can then say a service is the infrastructure that drives URLs.
Specific URLs are often called endpoints, while sets of related endpoints are known as API. When it comes to describing app architecture, services are often referred to as API, or application programming interface. This is to communicate the unity of purpose with those API found in software packages, that is, they act as interfaces for the join-points between software components.
We can broadly group API into consumer and infrastructure. Still broadly speaking, consumer API are used by “consuming” clients to fulfill their purpose, while internal API exist to provide the interactions necessary for the operations of the former — call these “infrastructure” API.
API (of both varieties) are the result, services are the cause. They are often used interchangeably. No Matter; we’ve got a firm hold on what they are.
The job of web applications is to provide and consume services. At the end of the day, that is why they exist.
The question is: how best to design and architect web applications that are durable, maintainable and effective?
In turns out to be a challenging question.
Internal Design and External Architecture
Microservice architecture (and its predecessor, Service Oriented Architecture or SOA), take the purpose of web applications, i.e., exposing services, and turns them into a mechanism of the application design.
We’re going to take a hard look at the why of this, and get honest about whether and when it makes good sense to do it. Because the last thing you and I want is to apply a technique to a situation because its cool, or trendy.
For the moment, we’re going to push on into the what of microservices, so that we’ll have a solid foundation for the why.
Understand that “micro” refers not just to the size of a service, but also the intent: micro-services are intended to contain a set of related capabilities, and expose them as a network resource. You then compose the application itself out of a collection of these network resources, aka, services.
In so doing, the distinction between internal application design and external application architecture becomes blurred. It’s hard to say exactly what an application’s outside is, versus, what is an application’s elements, or nodes.
That has always been the case: even the remote database, external services or browser client should be seen as elements of an applications architecture.
Remotely Decoupled Application Architecture
To circle around and make this point, consider an alternative name to microservice. If I were going to try to put a name on the idea, I might call it “remotely decoupled application architecture”.
Or, RDAA. Not nearly as catchy as “microservice,” but I think it drives home the idea.
The bottom line in software design is you must break things up into pieces, and then relate those pieces together to get the job done. This is done at every level in software, from functions on up. When it comes to the overall structure of an application, the question is: how to split it up to best effect?
For a long time, the answer to this question (for web apps) was: break it into layers. Each layer with a distinct focus, according to technical purpose (usually at least: data access, business logic, and UI). This proved to be fairly broadly applicable and effective. See Figure 1.
These layers are internal to the application “node”, that is, a discrete deployed unit. These layers are defined by the internal packages and modules, and enforced by the conventions of APIs within the app.
These API’s can be described as internal, or local API. They are responsible for the high-level internal structure of any application. Traditional apps rely on these local APIs for clarity and organization.
However, this has come to be derided as the “monolithic” architecture by those enamored of a new style: microservices.
This idea took root as the availability and ease of deploying remote API grew. Another name for remote API, as we saw earlier, is service.
Instead of divvying up application design into layers of technical responsibility, microservice groups application parts into nodes of logically related elements according to business concern. Also, and crucially, it deploys each of these nodes separately, and has them talk over the network.
Evolving the Monolith
Now let’s pause for a moment and consider an essential question: why do we break up software into pieces (including microservices)?
There are a multitude of answers to this question, but they boil down to these two things: make it understandable for people to use, and make it efficient for computers to run. In the first camp, you have a host of things like maintanability, reusability and so on.
So we can say that in separating the application into parts, these are our guiding lights: make it easier for humans to manage, and more performant for machines to run.
Let’s keep a good understanding of why we are considering application architecture in our back pockets.
An Example App
Let’s make things more concrete now, and consider an example. Saw we are building a webmail app. How would the layered “monolithic” design of this app look?
Looking more closely, the layered application design actually looks like so (Fig. 3):
Now what figure 3 adds is the truth that the data persistence and UI elements of the app are really separate from the core app, and that the core app has layers to deal with interacting with these elements, along with the layer for doing its business logic.
Business Logic Divisions
Now let’s develop this one step further, and add some detail about the actual business processes that are going on in the app (Fig. 4):
Business Logic Decoupling
By “business areas”, we simply mean different areas of concern. For example, in the email web app, you might have, at least: authentication/authorization, mail send, mail receive, and mail forwarding (Fig. 5):
Now what microservices suggests you do is the following, as seen in Figure 6.
Bear in mind this is a generalized simplification, but I trust you get the idea. Instead of each business purpose area being integrated into the same application node, they are broken out into units deployed independently, communicating via the network.
Something important to keep in mind is that the layered design of the nodes themselves remains! We don’t abandon the organizing principles that make the nodes themselves manageable.
The Layers are Still There
Some microservices may not require the same layers (a service might not expose an API that is consumed by the UI, for example). We can generalize the layers in a service (micro or otherwise) into three purposes: provide API, business logic, and consume API.
These layers will exist for all but the simplest “dead end” services, services which consume no other services for their work.
Having traveled down the road enough to see what microservice architecture is, let’s return to the fundamental question: why?
The question should be: “Where are the most effective remote API boundaries for the tasks at hand?”
Microservice Plusses and Minuses
It’s immediately apparent that microservice architecture implies additional infrastructure over a bundled-layers style. At the least, the services must each provision machines to run on and the networking between them.
The motivation is that each service is independently maintainable, deployable and decoupling between them is conclusively achieved.
The question in our heads should be not “Do we use microservices.” The question should be: “Where are the most effective remote API boundaries for the tasks at hand?”
Remembering the two purposes: human and computer effectiveness, we can say that microservices offers benefits to both.
In the first case, strongly decoupled components lend themselves well to teams that own them, and make for easier to understand services. We can focus on doing the job of the service and exposing its API, without tending to drift into worrying about the other bits.
Nevertheless, this can become a two-edged sword, when we have to consider adding related functionality across multiple services or refactoring shared resources.
Conceptually, organizationally, and process-wise, microservices can net benefits to development teams. Also, there are performance possibilities.
Smaller, more contained services means performance adjustments can be more fine tuned. That is, we can cluster smaller elements of the app that need it, as opposed to replicating the entire app stack when only a small part is under heavy load.
There is also the improved possibility of performance and efficiency gains that effect the bottom line: the smaller services can be run in a “serverless” environment where only those cycles of processing actually used incur charges from the provider.
For Real: When Should We Use MicroServices?
Now I’m going to insert myself into these discussion of motivation and offer some opinion.
The first is that microservices is a compelling idea, in its idealized form. It descends directly from the SOA (Service Oriented Architecture) idea. The core principle being to isolate application functions from eachother across network API boundaries.
The issues faced in microservices and SOA are the on-the-ground realities. Deploying services and managing them introduces complexity and overhead.
These facts have spurred the adoption and introduction of tooling like Docker and Kubernates. However, these are now additional tools introduced into your process and team.
My heart-felt opinion is that microservices really need something of a larger organization to be pulled off successfully, and a fairly intense operational environment to be merited.
By the later, I mean: many applications will operate fine as a single deployed unit, that can be clustered if necessary. There should be some compelling performance or organization reasons for breaking it up.
Inevitably, isolating the services across the network will compel someone or some part of someone’s time to be dealing with this arrangement instead of writing code. That must be figured into the calculus.
By the way: Did you know you can clap more than once (up to 50 times!) for this article???
I was going to end that right there, but let’s push our webmail example a bit further. I think it gives an improved feel for how it works in actual practice.
In Fig.7, the diagram is refined to reflect a more realistic arrangement. The fact is many of the services are not UI facing. The fwd, receive and send mail services are behind the scenes, acting as true “infrastructure” API.
We could think of this as the services’ exposure level, or type. That is: What other services and clients are they exposed to?
Also, notice now that each service is labelled with “provide” and “consume” vs. API and Data. This is more accurate.
Adding a Service
Now let’s say we want to sanitize our emails (stripping malicious JS and formatting HTML let’s say), coming in and going out.
We could add a sanitize mail feature, and instead of building a software package that is included in both the send and receive application nodes, we can deploy a new service and connect to the Sanitize Mail microservice.
Of course, this implies the work of spinning up VM/serverless environments and setting up networking to relate these services in dev, test and production.
Two Kinds of Service: Ours and Theirs
Another useful division of service types is according to those “owned” by us, and those owned by someone else. We can say we are responsible for the infrastructure and internals of our services, while we consume via public or pay-for services that belong to others.
See figure 9.
Finally, we could add a bit of retail to the external services we consume, as in Fig 10. Also in Figure 9 and 10 we can see that the UI service is responsible for “UI set-up” to reflect the truth that UI applications are responsible for delivering the assets (JS, HTML, images, etc) to setup the UI client in the first place.
End extra content :)