In the case of microservices, the tale is not as old as time and the tune is not as old as rhyme. Only recently we began to see an explosion of conference topics, articles, technology talks, and blogs about microservices. According to Martin Fowler, microservices are a type of software architecture that involves “designing software applications as suites of independently deployable services.” Like any other form of architecture, there are benefits and risks to adopting microservices. Therefore, as development teams we must fully understand the advantages and disadvantages of microservices before choosing to adopt such an architectural style.
Let’s begin by listing some of the benefits of using microservices:
Microservices promote continuous delivery and system stability, enabling frequent releases without affecting the rest of the system.
The services allow individual development teams to work in parallel and independent of each other as they deliver new features.
Under this model, organizations that are highly dependent on technology for profit can get new features to production quickly.
The services are very simple and are designed to perform one specific functionality.
Since the services are independent, each service can be implemented using the most suitable tool.
Having said that, we must not forget that behind every beauty is a beast!
The risks of microservices can be categorized into extrinsic complexities and intrinsic complexities.
A major extrinsic complexity of using microservices is managing the services and channeling business processes correctly between them. Having multiple services to test, to deploy, and to run in different languages and environments produces a lot of operation overhead. With the large number of processes running, making sure the system does not run out of space, the processes do not deadlock, and the performance of the whole system does not degenerate becomes more challenging. Furthermore, successfully promoting the services all the way to production requires extensive deployment automation.
Thus, before delivering any business features, development teams will have to spend a lot of time and effort designing and implementing custom deployment mechanisms to manage the services, the underlying processes, and the whole system. Since many services in a given system will need their own datastores, additional overhead arises from the necessity of maintaining and backing up the data.
With Microservices, a plethora of different messaging mechanisms are required to facilitate the communication among processes and servers. Developers have to design solutions and code for unreliable networks, load balancing, versioning, and message serialization to mitigate issues resulting from distributed system complexity.
In addition, the dynamic nature of microservices means testing challenges. Testing services in isolation becomes a simple task when measured against testing the system as a whole. With many services interacting with one another, it becomes hard to predict system behavior, recreate environments, and reproduce bugs. Additional risk is imposed if development teams do not have the right design, service decomposition, and boundary definitions right off the bat before environment setup and system development commence.
Addressing the Risks of Microservices
How can some of the risks of microservices architecture be alleviated?
Deployment automation addresses the problem of having multiple services to deploy. Development teams can utilize tools like Jenkins, uDeploy, Capistrano, or custom scripts to automate deployment.
To monitor services for performance degeneration, performance monitoring becomes a requirement. Nagios, StatsD, Graphite or cloud-based services such as New Relic provide a dashboard where performance can be monitored and problems exposed. To monitor distributed systems in real time, the following tools can be used: Netflix Suro, Riemann.io, Sensu, and Circonus.
Since each microservice generates its own logs, the ability to aggregate and search logs becomes very important to provide insights into what went wrong. Tools like Logstash allows development teams to store log files in one single location. Tools like Elasticsearch and Kibana can be utilized to search the log files.
One way to tackle service decomposition is to start with large monstrous services and decompose them into smaller ones until each service is responsible for a single functionality.
To address testing challenges, development teams should devise testing strategies at the intra-service and inter-service levels. Intra-service testing is performed to verify that the communication and the functionality between the different components that make up a single service are working properly. Inter-service testing refers to testing the interactions and communication among multiple services as they work collectively to provide valuable business features. This also includes testing the communication between the services and their datastores, which are considered external entities as well. Test automation must be used extensively where and when possible. Unit tests, integration tests, component tests, contract tests, end-to-end tests, and ad hoc tests are all part of the recipe to successfully address testing challenges.
In the context of microservices, what do these different types of tests mean and achieve?
Unit tests determine whether the tiniest chunks of testable software (class or a group of related classes) in the application are working as expected.
Integration tests validate the communication and interactions among services and datastores to expose interface defects.
Component tests involve the isolation of a fully functional subsystem of the software using test doubles or mocks (similar to the concept of stunt doubles in movies) and manipulating the subsystem through internal code interfaces.
Contract tests validate interactions at the boundary of an external service, confirming that it meets the contract expected by a consuming service.
End-to-end tests verify that the software meets the requirements and does what it is supposed to do through testing the system end to end.
Ad hoc tests involve randomly exploring the system without a test plan. These types of tests help development teams refine their automation tests.
The risks of microservices will be mitigated as more real world applications, case studies, and tools emerge. The beast does become a prince in the end. However, before development teams decide to adopt microservices, they must fully understand the risks and the benefits of the microservices architectural model.