Microservices are decoupled components that follows the Single Responsibility Principle (SRP), that is, a component does one thing but does it really well. In this mini-book we will be discussing various patterns for decomposing a service into a microservice.

What motivates developers to go Micro?

Imagine the case where you hire someone to start hacking and adding code to your existing framework, I am sure you don’t need me to tell you that it takes sh*t loads of time for them to even start adding one line of code. Forget about that, and lets consider the case where you need to develop your test cases? not so swift huh?! Okay, you are still not convinced, what if you want to reuse code components to build another application… yes, you need to consider modifying the whole application if you are on the Mono side. Akhh, you are not see easily convinced I see, fine, what if your application was built using C++ and you discovered this new awesome language and want to transform some of your application components to be built using this language? yes, you will need to change all of it :P. I got you there didn’t I.

So let’s recap, whats the benefits of microservices?

So now you want to decompose your monolithic application, whats next? You need some patterns amigo!

In the rest of this document we will be going through patterns of microservices decomposition. If that didn’t cover it for you and you want to dig deeper, have a look at the references section, with a special recommendation of Chris Richardson’s blog.

How to decompose your services?

Thats a very tough question to answer because the answer is “it depends”. Many people came up with different methods for doing the decomposition, there is no “best” I would say, but below are some methods I found after researching a bit, I added some of my own as well.

Decompose by Business capability

N.B: You need to understand the core components very well.

Decompose by locality

N.B: you need to understand the weight of the links between each of your sub-components. If the weight is too high, then consider having them as one service component.

Decompose by Greed

N.B: you need to study the resource consumption of each sub-component before attempting the isolation

More?

Of course there are more ways for decomposition, there is no silver bullet though, but there are best practices, and guts feeling :)

Deployment patterns

Deployment decision matters, the way you deploy your services will affect your overall application performance. Lets talk some about the different methods for service deployment.

Now that you have decomposed your application, where should you place them? you have many options,

Single service instance per host (service-to-host)

There are two ways to deploy a microservice per host.

Multiple service instances per-host (services-to-host)

Once you have your monolith decomposed to micros (i.e., microservices), you can decide to keep services of the same application on the same host, of course, that depends on the design principle you followed from the beginning. For example, if you made the decision to “Decompose by locality”, then it makes sense to keep services that communicate a lot together on the same host.

When deciding to use this approach, make sure you understand the resource consumption model for each of your services. Isolation matters here. It makes sense to use VMs or lighter-weight containers for achieving this isolation, but again you will have to take the decision of whether to maintain a one-to-one mapping between your services and containers or not (basically the same decision process as the service/services-to-host just on a higher level)

An alternative is to choose to place each service on a separate host.

Serverless deployment

Sometimes, you don’t feel you have the skills/time for making decisions about how to place your services, well worry not, in that case a “Serverless” deployment is the recommended method.

A “Serverless” deployment offload the overhead of the “How” and “Where” to place your services to black-box remote infrastructres that are well equipped with schedulers for making the decisions for you. All you need to do is set some performance expectations and watch them happening in numbers. Some example? okay, here are few:

Life is not flowers and colors though, when you make the decision to go serverless you are giving up on ALOT of choices that you could have made take the “Server-based” deployment:

Eventually, a serverless architecture is also server-based behind the curtains (they have to run somewhere right?!), it is just that they (i.e., amazon, google, microsoft) do the magic of hiding the servers from you and only require that upload your code to have a fully functional decomposed-application.

Handling Cross-cutting concerns

Some parts of your application can be easily decomposed because there is very little dependency on them. Other parts are being used across the whole application by most of the services in the decomposed architecture. These parts are often referred to as the “Cross-cutting concerns” of your application, examples of these are:

To avoid future complexity resulting from service-decomposition, it is highly advised that you incorporate design and support for cross-cutting concerns before starting to write your code.

Is there an easy way to do that? Well, you can port out the effort to microservice chassis frameworks such as Spring Boot, Dropizard Micro, etc. The easy way is not always price free, adapting a microservice chassis service will fix you to one programming language, I have not seen yet chassis that are programming language independent.

Another way to do it is to Externalize cross-concern configuration such that a newly started service can read the configuration from the externalized store and start functioning (e.g., through the use of environment variables)

Handling communication between micro-services ?

Communication is a crucial element in building efficient microservices. However, communication can be a broad-term, therefore, lets divide microservices communication to more specific scopes, but before doing this, lets look at a generic example (the company example) to better understand the scoping logic.

The company example

Imagine you are the CEO of a big company, being the CEO, you are responsible for communicating with other CEOs in other companies for striking some winning deals, they know nothing about your internal policy of management but yet they still can communicate with you, i.e., you are your company’s entry point (the external reference). Alternatively, your company is divided into departments, these departments each have a manager, the managers from the different departments communicate together using a well defined company internal communication model. Is that all? not really. Imagine that your company is huge, it is spanning multiple continents each continent branch acts as a separate entity and thus have a local CEO. The local CEO should be able to speak the CEO language because they act as entry points for their continent branch but still provide the internal mode for the branch communication.

Services are not different. Each service has external clients, thus they should provide method for external communication (e.g., REST, and RPC). On the other hand, the communication between the decomposed services should be swift enough to avoid degradation in performance (e.g., protocol buffers, event-based, etc)

External communication (inter-service communication)

It is the model in which external clients communicate with the overall service. Examples include:

Internal communication (intra-service communication)

Communication between the decomposed parts of the services.

Mixed-model

Decomposing the service into larger blocks each is recursively decomposed into smaller services. This results in:

Known communication methods:

Well, from what I have seen so far, there are three methods for services to communicate with one another:

Each one of these methods can have its own model of communication:

The analysis of the advantages and disadvantages of each of these methods is out of the scope of this document.

Service-Discovery - Where is my-service?

In a monolithic architecture, components of a modular application usually reside on the same place and thus communicate together on the language level, through method calls or procedural calls. In the context of microservices, the case is much different, services are usually virtualized and isolated into containers or virtual machine which are dynamically placed and thus not statically addressed, which brings up the question of “where is my-service”.

There are multiple methods to solve this problem:

Both server and client side discovery are required to communicate witha service registry. So what is a service registry? It is a database that contains information about service instances. There are two ways for services to be updated in the registry:

Handling failure across dependent services (Circuit breaking reliability)

Some microservices communication with other micorservices to achieve certain tasks. Failure in of of these services my result in the failure of the other services communicating with it. Therefore, it is necessary to detect these failures, and redirect the communication to other services that serve the same purpose. This procedure is called “Circuit Breaking”, where failure in communication between two dependent services is handled by a timeout value just like a real-life circuit breaker.

Handling storage-dependencies for microservices

One of the key-benefits of deploying a microservices architecture is loose-coupling. This property is what allows you to deploy and scale services independently.

A question that might arise is how to handle service storage? there are multiple possible answers, below are some solutions to consider:

Handling access to services

In a microservices architecture, services provide an interface for allowing clients to access them. However, should an client be allowed execute an service? that should not be the case, there must be a way to authenticate requests coming from clients. There are multiple ways for implementing this including the use of access tokens.

Service Function Validation

Before deploying our microservices, we must ensure that these services will function as expected. This can be done via an automated continuous testing approach including stress tests, unit tests, integration tests, etc.

Logging, Instrumentation, and health-checking (Observability)

Problems are meant to occur whether a monolithic approach or a mirco-services approach is utilized. Detecting these problems is the tricky part.

The task of health-checking can be done by the service registry or a separate health service.

Is it really worth it?

Before jumping on the microservices bandwagon, it is crucial to identify the benefits associated with decomposition. Not understanding these benefits would render the whole transformation useless. In this seciton, we provide anti-patterns that we as developers / researchers should avoid when looking at microservices.

Build a reference-point to start from

Sometimes, people working on the same project might not agree on what microservices would mean in terms of development. As a result, a multitude of opinions would merge-in leading to a spaghetti implementation of a complex UN-managable system.

Therefore, it is essential to define the goals, agree on a reference definition to avoid the confusion and frustration in later phases of the project.

Don’t give up on the API

Before getting caught up in the implementation define the API that your services will expose, these are contract that your services should respect. When starting a new service, define a simple API, and evolve it as needed.

Don’t Choose any communication method

Communication is one of the key performance influencers. Therefore, choosing the right tool for the task matters. Exchanged messages can be encoded in a variety of ways, service consumption can happen in different ways as well.

Understand the service consumption model and requirements before choosing the communication method.

N.B: read the communication section of this document.

Raise them to be independent

Coupling is one of the reason we are getting away from monolithic architectures, this way we can implement scaliny, reusability, automisty, and all the benefits mentioned before.

To avoid a tightely coupled architecture make sure that the concerns are separated (check the decomposition methods in this document).

Don’t neglect testing

Microservices are makes your system more complex to manage, as a result, if you don’t test now (as in before deploying your service), you will surely regret it. Once you let your service spin without good testing, it might be hard to handle the errors resulting from it, and your whole system will be affected.

N.B: make use of continious delivery methods

Don’t Team out, Team up

Although microservices promotes productivity by separating concerns in service developement, it is still necessar for developer to co-ordinate. The less-coupled the services, the less communication needed :)

Do these and it is maybe worth it!

References