25
2016
Moving towards Microservices – Challenges and Common Pitfalls
The concept of “Microservices” has been a regular customer in the busy streets of Silicon Valley during the past few years. In an industry where dozens of languages, frameworks, and tools come up every day, it seems Microservices are here to stay. So what are these microservices? This article tries to find an answer for this exact question in layman’s terms.
For most of us who love coding, the traditional approach for building software has been packaging everything into a single executable file that would ultimately be deployed in some kind of runtime environment. These types of applications are called monolithic applications. Until the recent times this has been the preferred way of building enterprise applications.
Though monolithic applications are relatively easy to build, they come with few inherent issues. One of the key disadvantages of monolithic applications is the fact that you are tied to the technology stack you chose in the initial stage. How often we battle with older technology stacks when there are new frameworks out there to get the job done with so much of ease? Another issue with monolithic applications is the fact that it’s harder to add changes rapidly. Changing one single line of code would need the whole artifact to be redeployed. In a time where everything is deployed in the cloud, uploading bulky artifacts can considerably slow down the deployment process.
Microservices take an approach where a monolithic application is divided into a set of small services that communicate with each other to implement business functionality. Microservices architecture tries to provide solutions for most of the issues that exist in monolithic applications. Since the application is divided into a set of independent artifacts, developers are no longer required to stick to one particular technology stack. Microservices also equips the developers with a mechanism to deploy code changes swiftly. Since developers can identify the exact service that requires the change, only that particular service is needed to get redeployed. These are just a few from the long list of advantages of microservices.
So what makes a good microservices? According to Sam Newman (Author of “Building Microservices”), it boils down to two things. High cohesion and loose coupling. For a microservice to be highly cohesive it should contain only a related feature set. This makes sure that developers don’t need to modify many services to get a change request done. Newman also stresses that microservices should be loosely coupled which means that each service should know only as little as possible regarding the other services. This makes sure that changes in one service do not affect others.
Implementing a real life microservice based solution poses far more challenges than what’s described in the previous paragraphs. One of the key architectural decision one should make when building a microservice based solution is the technology used for integrating those services. Since by definition microservices are a set of independent services communicating with each other, deciding the mode of communication between the services holds the key to success. Following are few points to remember when selecting an integration technology.
Smart endpoints and dumb pipes
In relation to technologies like enterprise service bus where smartness is in the communication channel, microservice community prefers dump pipes and smart endpoints. Having dumb pipes means that we can change the mode of communication without affecting the services.
Keep technology agnostic APIs
As discussed earlier, one key advantage of microservices is the fact that it enables developers to use different technologies. When deciding on integrating different microservices, one should try to select a technology that doesn’t depend on the individual implementation of the microservices.
Make services simple to consume
No matter how sound are our individual services, if the cost of consuming those services are expensive, their individual brilliance would not make much sense. It is ideal if the service consumers can select technology stacks independently to use other services. If the integration technology could accommodate these requirements, it could be considered as a good candidate.
Hide internal implementation detail
If internal implementation details of a microservice are exposed to its consumers beyond what’s needed for operation, its consumers increase the binding to the internal implementation of the microservice thus increases the coupling among services.
Once you have decided on the integration technology there’s one more decision to make before you dive into the implementation. Should the communication be synchronous or asynchronous? With synchronous communication, when a call is made to a service, the caller is blocked until the operation is completed. With asynchronous communication, the caller doesn’t wait until the operation is finished. Each of these methods has their pros and cons and one should evaluate which approach suits best for the job in hand and the business needs of the application.
Challenges and common patterns
There are lots of aspects you need to consider when building a real-life microservices-based solution such as design, security, deployment and testing. Each of these elements would require its own deep analysis. So instead of going into details about these elements, let’s discuss the challenges teams face when building microservices-based solutions. These challenges are not merely design related but also technical and organizational.
One of the key challenges faced by teams who are new to microservices is deciding the size and scope of microservices. The key would be to create microservices according to the bounded contexts of the domain. But identifying and narrowing down the bounded contexts are not that straightforward for a team who are new to microservices. As a starting point, it’s advisable to first design the system as a monolithic and then separate out modules for identifiable contexts. As the team matures each of these modules could be developed as separate microservices.
Since a microservices-based application has many independent and distributed “services”, the development team needs to design and define them in a centralized manner to avoid confusions when different teams are working on them. APIs of the services are the primary basis of communicating to them by the different components of the application, thus maintaining the knowledge of their design in the long span of the project is a big challenge for the success of the project. A good API documentation tool like Swagger can help teams to mitigate such challenges.
Another key challenge in adapting to microservices from the monolithic world is the complexities in microservice integration. Most development teams underestimate this but in practice lot of effort is needed to mediate, to format conversions, synchronize, handle access timeouts, etc. when dealing with multiple microservices.
Testing the microservices can become a challenge for many teams who are new to microservices. The key is to cover each microservice with a high percentage of unit tests for that microservice. Some percentage of high-level, end-to-end test cases are also needed to assure integration among services. A test driven development approach could help teams to create more reliable microservices.
Monitoring is also one of the biggest challenges when moving from monolithic to microservices. Services communicate with each other as well as with the outside world. Keeping track of requests could become increasingly difficult in a microservice environment. One way to overcome this issue is adding an identifier to the request in the HTTP header and log it in each service. In this way, it would be helpful to trace what happens to the request in the full application cycle.
As microservices are commonly deployed in virtual or container based environments their end-point locations can be virtual and dynamic. Therefore, being aware of the dynamically changing end-point locations of other services can be challenging. In order for one microservice to find another, they can use service discovery protocols. Service discovery can be implemented using a service registry, it is essentially a database that holds information of service instances and locations. Service registration by self or service registration via third party solutions are two approaches in service discovery.
Few examples of service registries are Eureka, Apache ZooKeeper, and Consul.
Another area that poses challenges for microservices is the deployment of artifacts. Since we have dozens of artifacts to manage now, it’s a must to implement continuous integration and continuous deployment. Jenkins and Docker are few tools that can help teams to deliver artifacts faster to the production environment.
Apart from all architectural and technical challenges, organizational challenges are also very critical. The organizations should accept the fact, building microservice based systems will take more time and effort than traditional monolithic applications. Most benefits that microservice based architectures provide, come later in the maintenance phase of the system in the long run.
The organizations should have good DevOps teams to maintain the many microservices. Since most of the organizations follow monolithic applications, they need to change their culture and structure to adopt microservices. Even though microservices need to be implemented as separate services, each team should have a good understanding of what the other services do and the team should have end-to-end responsibility in delivering microservices.
In the implementation of building and deploying microservices, we may face different challenges as this article has summarized. But instead of seeing them as problems, we can see them as opportunities to build software better in the long run.
Authors: Chathuraka Waas, Vithiya Sivapalan & Ishadi Jayasinghe