Final Report

Microservices is an architectural style that has become very popular in the past few years, partly due to the advent of the cloud and container technologies making in possible. However, is this architectural style suitable for all type of applications? Even if it is suitable for your organisation, what are the prerequisites and how do you get from monolith to a more loosely-coupled, distributed microservices architecture?

Once you make the decision to move towards a microservices architecture, there are three categories of concerns to know and plan for: design, development and operational. A study done on grey literature done by Soldani, Tamburri, & Van Den Heuvel (2018) shows the coverage of the concerns.

Picture 1
Figure 1 – Coverage of the concerns in the taxonomy for pains of microservices

A. When to migrate

Before we get to these three concerns, we need to answer: when you should migrate to a microservices style architecture? To answer that we need to know when it is okay to run a monolith application. Afterall, not all applications are created as a microservices.

According to Kazanavičius & Mažeika (2019, p. 1) monolithic architecture is fine when you have an application that is simple and lightweight (so it doesn’t require scaling), and microservices are suitable when the application is complex and evolving. Think of a game software or an OS kernel. These types of applications don’t need to be re-designed to run as a microservices.

Another type of application that may not be very suitable for microservices design is critical system applications. Such systems will have very low margin of errors (one acceptable error in 109 operational hours) and now to ensure correctness of the system, you also need to make sure the underlying host environment (being cloud or docker container running on top of Linux) is also error free. A brief analysis of Linux shows there is always about 5000 defects waiting to be fixed. (Fetzer, 2016)

Another reason to not migrate to a microservices architecture is related to scaling. Typically, microservices, due to its nature being distributed and decoupled, can be scaled horizontally (running multiple services to manage the load) whereas monoliths can only be limitedly scaled and only vertically (getting more CPU/RAM or faster disks). A comparative research done by Al-Debagy & Martinek (2018) shows that both architectures behave similarly under normal load, however microservices perform better under load and concurrency.

Kazanavičius & Mažeika also summarize that you should migrate to microservices when:

  • The monolith is too complex to maintain
  • You benefit from decentralization and modularization of the monolith
  • You see importance in the long run (as there’ll be a lot of growing-pain for the short time)

Another way to look at this comparison is based on the database and underlying domain transactional consistency requirements. Often it is the case that monoliths rely on ACID (atomicity, consistency, isolation and durability) which means the data is always updated in a consistent manner. Due to the distributed nature of microservices and the fact that services run on multiple machines (sometimes local as well as the cloud), it is not possible to achieve reliable transactionality. The distributed systems instead rely on BASE (basic availability, soft state and eventual consistency) which means that the data might be in an incomplete or inconsistent state at some point (while transactions are in flight). If the complexity you are dealing with is due to large flows of data or rapidly changing data structures, you need to consider microservices. (Singleton, 2016)

B. Organisational Concerns

An often side-effect of designing a system with microservices is that instead of having to run a monolith, which often consists of a single physical artefact, you end up having tens and sometimes hundreds of physical services to deploy, run and maintain. To add to this complexity, components that were tightly-coupled now are potentially running on a remote machine as a distributed service. This requires a different organisational skill set and resource group to manage.

The software development community has already established patterns such as Continuous Development, Continuous Delivery, Canary releases and Blue/Green deployment methods to address some of these concerns (Kargar & Hanifizade , 2018). Your organization should already have these practices in place and established, as that takes time.

C. Design Constraints

Designing of a software system is hard on its own, but to add to the complexity, now you need to find system boundaries and how your system is composed by multiple services. When migrating an existing monolith to a microservices design, you may find that reverse engineering might help understand the system better. In a study done by Di Francesco, Lago, & Malavolta (2018) techniques such as domain decomposition, identification of new services and application of domain-driven design practices were the most common activities performed when designing a microservices system.

Picture 2
Figure 2 – Activities performed when designing a new system

Another question to answer that impacts your design decisions is how to do the migrations. The benefits of gradual refactoring vs a big-bang rewrite has been well established but if your application already has bad design, or you are running on a very old platform that you can not develop on anymore (due to the lack of development tools or developers having in-depth knowledge of the said platform), a rewrite may be justified. The suggested approach, if it is a rewrite or gradual refactoring, is to start your newly identified services with the new architecture. In the survey conducted by Di Francesco, Lago, & Malavolta (2018), 19 out of 30 participants started by implementing their microservices when introducing new services or reimplementing new functionalities and only 5 were doing a big-bang rewrite.

Picture 3
 Figure 3 – How did the respondent starts implementing Microservices

One key aspect when running microservices-based design is backwards compatibility. As stated before, the microservices system might be consisted of tens or hundreds of services, where each set of services managed by a separate autonomous team. The team has the freedom to deploy updates to the services as they see fit, without going through a concerted effort of releasing all the services at once. This means at one time, different services may rely on different versions of other services for their functionalities, so if a team deploys a new version of the service that breaks the service contract, the dependant services will fail and this failure can cascade to other dependant services, ultimately causing the whole system to fail. The solution to this is backwards compatibility and versioning of the public API or service contracts. Kargar & Hanifizade (2018) advise that “the microservices must have backward compatibility, so that each version of microservices must also support the previous version inputs” and this can be enforced with the use of regression and integration testing.

D. Development concerns

Autonomous services mean that the development team should be able to decide what technology works for them. For example, if document database is a good fit for a service, they should be able to choose that. Companies such as Netflix go as far as to say the team owning the service can choose the platform and language of their choice. This is all possible because the services are decoupled and would only expose a set of APIs to the other services.

There are seven tenets of developing microservices that is a recurring theme in studies around microservices. Those are: Fine-grained service interfaces, domain-driven design practices, cloud-native design, multiple-computing paradigms such as storage paradigms (relational database vs no-sql storage), lightweight containers, decentralized continuous delivery and DevOps. (Zimmermann, 2017)

E. Operational concerns

There are three main issues when running a distributed system in production. Monitoring, Logging and Security.

A monolith usually just runs on a machine. This means that you only have to monitor one machine to ensure smooth operation of the application. The components of the system is all tightly-coupled and run within the same process so there’s no cross-process communication. Microservices is very different. There are tens or hundreds of services running that may be running on various machines. In a mixed-environment, some services might be running locally, some on the cloud as a container or a cloud-service, and some even may run as cloud-edge. These are vastly different services that all need to be monitored to make sure the system is operational. The organisation now needs to monitor hundreds to thousands of different resources and this is not an easy task due to the system being composed of heterogeneous services. Indirect monitoring of service can report on metrics such as CPU usage or memory. While it does not require active integration with the underlying service and it is less intrusive, it may not be enough. (Cinque, Della Corte, & Pecchia, 2019).

Logging is another aspect that is quite different when comparing to running a monolith. Distributed nature of microservices makes end-to-end tracing of a process very hard, as bits of the whole process or workflow now may be owned by a different service. It is recommended to use techniques such as contextual and structural logging, using unique identifiers is requests/responses and writing logs to a local storage. (Kazanavičius & Mažeika, 2019). Using a logging server that provides searching/filtering on top of the structural logging is strongly recommended as well.

Security is another major challenge with regards to microservices. The number of services, whether running locally, in the cloud, or in mixed environment, is a cause for concern as it enlarges the attack surface. To mitigate this, there are three approaches to establish trust between various microservices: using MTLS (mutual transport layer security) to achieve authentication, using Security Tokens (ST), or using fine-grained authentication. (Yarygina & Bagge, 2018)

References

Al-Debagy, O., & Martinek, P. (2018). A Comparative Review of Microservices and Monolithic Architectures. 18th IEEE International Symposium on Computational Intelligence and Informatics. IEEE Xplor.

Cinque, M., Della Corte, R., & Pecchia, A. (2019). Advancing Monitoring in Microservices Systems. International Symposium on Software Reliability Engineering Workshops (pp. 122-123). IEEE.

Di Francesco, P., Lago, P., & Malavolta , I. (2018). Migrating towards Microservice Architectures: an Industrial Survey. International Conference on Software Architecture (pp. 32,33,34). IEEE.

Fetzer, C. (2016). Building Critical Applications Using Microservices. (p. 86). IEEE Xplore.

Kargar, M., & Hanifizade, A. (2018). Automation of Regression test in Microservice Architecture. 4th International Conference on Web Research (ICWR) (p. 134). IEEE.

Kazanavičius, J., & Mažeika, D. (2019). Migrating Legacy Software to Microservices Architecture. Open Conference of Electrical, Electronic and Information Sciences. IEEE.

Singleton, A. (2016). The Economics of Microservices. Cloud Economics, p. 20.

Soldani, J., Tamburri, D. A., & Van Den Heuvel, W.-J. (2018). The pains and gains of microservices: A Systematic grey literature review. The Journal of Systems and Software.

Yarygina , T., & Bagge, A. H. (2018). Overcoming Security Challenges in Microservice Architectures. Symposium on Service-Oriented System Engineering (pp. 11-20). IEEE.

Zimmermann, O. (2017). Microservices tenets – Agile approach to service development and deployment. Comput Science Research and Development. Springer.

 

Monolith and Microservices: a comparative review

As stated before, just because microservices is the latest trend, doesn’t mean that you have to change your system architecture accordingly. Other than the organisational and technical challenges ahead, it may not necessarily even result in a better system performance for your application.

Although there are not that many researches around this area, a comparison done by Al-Debagy & Martinek (2018) shows interesting trends. The research that focuses on the performance of microservices and monolithic applications and uses JHipster to generate web applications consisting of Angular framework and Spring Boot and had three services: A register of all the components called JHipster registry, a backend service that provides API to the frontend and a microservices API gateway. The JMeter software was then used to test the performance and measure response time and throughput. Also,  load testing and concurrency was done to see how the application behaved under heavy load.

Number of request per service
(Al-Debagy & Martinek, 2018)

The conclusion was that both architectural style can behave similarly under normal load. The only scenario when microservices shine is under heavy-load and concurrency. What needs to be highlighted more is that the monolithic application can respond faster (due to lower over-head as there’s no remote call) so it provides higher throughput on average.

Rererences

Al-Debagy, O., & Martinek, P. (2018, 21-22 Nov. 2018). A Comparative Review of Microservices and Monolithic Architectures. 2018 IEEE 18th International Symposium on Computational Intelligence and Informatics (CINTI)

Monolith migration: rewrite or refactor?

When talking about big changes in a software, there are two approaches: gradual refactoring or big bang rewrites. Which one is suitable when migrating to microservices? As with any sensible answer: it depends.

If your application already has a poor performance or design issues (such as being very tightly coupled to a database), or if the application is written on a very old platform and language, it may not even be possible to do gradual rewrites. Imaging a system that is written with Fortran language and you’d have to find developer that are fluent in Fortran to be able to understand and refactor the code-base.

On the other hand, if you are on a somewhat modern technology stack and have a decent architecture, it’d be quite possible to gradually migrate to a microservices design.

Kazanavičius & Mažeika (2019) state that the main challenge when gradually migrating to microservices is “the extraction of microservices from existing legacy monolithic code bases”.

References

Kazanavičius, J., & Mažeika, D. (2019, 25-25 April 2019). Migrating Legacy Software to Microservices Architecture. 2019 Open Conference of Electrical, Electronic and Information Sciences (eStream)

Monolith migartion to microservices: tools

You have finally decided that it is time to move to a microservices design and reap all the benefits. Let’s go through the the code and start refactoring…but wait. Before you do that, let’s see what are the tools that you need in place beforehand.

The requirements can be summarized as, infrastructural, environments, monitoring and logging. Let’s review each quickly.

Infrastructural needs

As stated before, running a microservices-base application is not exactly like running a monolith. So you will be working quite differently when developing a microservices application. First thing that can probably be put in place, is a Continuous Integration/Delivery pipeline if your organization does not have one. As this pipeline will be critical to your organization when releasing updates, make sure you have a group responsible for maintaining it.

Environments

Even when running monoliths, a lot of organizations choose to run their applications on virtual machines. That is a good start, if you are running on bare-metal. To take a further step, you can use docker as a virtualization technology and with these pave the way to running your application in the cloud. Once you decompose your monolith, you can decide which parts should be running on the virtual machines, which services should run in a docker container and which services can be run directly in the cloud.

Monitoring and Logging

With microservices you’d have to monitor tens to hundreds of services, machines, virtual machines, docker containers, database, etc and this is not an easy task. The monitoring tool that you choose should not only monitor services being up and running, but that they are actually doing work. Logging is also different as each piece of the system will be running on various services (not to mentioned scaling where you have more than one service running for the same task). A better approach to logging is to use structured logging backed by a structure log server that allows searching. Your logs should also use correlation and context so that you can trace a workflow end-to-end, regardless of where it is run.

Migration of Monolith to Microservices

As a result of microservices becoming so popular, the monolithic architecture got a bad rap, but there are lots of software systems that are perfectly fine to run as a monolith.Think about it: you only have one application to release, deploy, maintain and monitor. Due to the components of the system being tightly coupled, you can test things a lot easier and problems are easier to find. The question is, which architectural style is suitable for what purpose?

According to Kazanavičius & Mažeika (2019) monolithic architecture is fine when you have an application that is simple and lightweight (so it doesn’t require scaling), and microservices are suitable when the application is complex and evolving. (p. 2)

After some years of developing the said monolith, things would become too hard to manage, partly due to the tight-coupling and side-effects of it (e.g. when you fix a bug, ten more appear). But when would you migrate such monoliths to a more microservices based design?

Kazanavičius & Mažeika also summarize that you should migrate to microservices when:

  • The monolith is too complex to maintain
  • You benefit from decentralization and modularization of the monolith
  • You see importance in the long run (as there’ll be a lot of growing-pain for the short time)

References

Kazanavičius, J., & Mažeika, D. (2019, 25-25 April 2019). Migrating Legacy Software to Microservices Architecture. 2019 Open Conference of Electrical, Electronic and Information Sciences (eStream)

Backwards compatibility in Microservices

As stated in the previous posts, a side effect of designing a system using microservices is that you end up having tens and hundreds of services that you need to keep operational. When you deploy a new version of a service that contains a bug fix in particular service, or when you want to improve another by adding a feature to it, the ‘system’ as a whole would still require to be functional. But how do you ensure that?

The problem is that if there is a fault at one of the services, other services communicating with it may fail and this would end up having a domino-effect bringing down the whole system. Furthermore, what if due to that bug-fix or enhancement, you need to update the service interfaces of that microservice. This would be a breaking change so any other service relying on it won’t be able to communicate with it anymore and would have to update.

Is it possible to spend time and resources to run the new release (of the whole system) in pre-production environment? To simulate things running in production, now you’d have to have two sets of production environment, which would have running and maintenance costs.

Kargar & Hanifizade (2018) advise that “the microservices must have backward compatibility, so that each version of microservices must also support the previous version inputs” and this can be enforced with the use of regression and integration testing.

References
Kargar, M. J., & Hanifizade, A. (2018, 25-26 April 2018). Automation of regression test in microservice architecture. 2018 4th International Conference on Web Research (ICWR)