In a previous article we discussed some of the positive characteristics of microservices that we’ve found while implementing them in a production setting. Two of the primary benefits we discussed are the architectural agility and enforcement of api boundaries. While you may find those and many more benefits from using microservices, you will also find that the positives don’t come for free. In this article, we’ll discuss some of the challenges that we’ve dealt with in implementing a microservice-based architecture. Being aware of these will save you the time and frustration that we experienced early on in our experience with micro services!
You’ve probably already seen this if you’ve had a chance to investigate microservices at all, but first and foremost, the deployment of each microservice needs to be completely independent of other microservices. Making this a hard rule gives the overall project more agility when change is necessary. This means no explicit dependence on another part of the application, and yes, no common domain objects (to be discussed below). Once services start to become dependent on the state of or code involved in another service, you essentially lose your primary benefit from the microservice architecture – small, independent deployments and an agile architecture.
From our early days learning software development we are told not to repeat ourselves (DRY). When using a microservice architecture, this rule is broken (strongly in some cases). You must be able to deploy services independently. Instead of using single, shared domain objects, you should use separate domain objects in each service that encapsulate only the information necessary for that service (e.g., the shipping business logic may not need to know about a products current price, but may need to know about the products current inventory level). Early on in our project we didn’t abide by this with the thought that one of our domain objects wouldn’t change very often. Well, we changed the domain object soon thereafter and were inevitably left attempting to orchestrate the deployment of multiple services in a specific order. In short, use common domain objects between services at your own risk – we strongly advise against it!
Lets just say that testing microservices is not easy. The number of services and the varying levels at which they need tested is a challenge that grows with each service that is added to or removed from the architecture. The Testing Pyramid introduced by Mike Cohn and made popular by Martin Fowler shown in the figure is an indispensable model our team uses as a guide for testing microservices
By testing each service at varying levels in the hierarchy we pull out a holistic view of the health of our microservices. It’s important to not overlook the value of any one layer. Expect to invest heavily in testing at each of the layers noted. Any and all time spent developing automated tests for each of the layers will be time well spent and a wise investment as the architecture grows and evolves.
One of the largest benefits of using a microservice architecture is its agility. Developers can make small changes and deploy a single service rather than having to deploy an entire application at a given pre-specified interval. The increased number of deployments, however, leads to unique infrastructure considerations that need to be taken into account. Handling service infrastructure and deployment by hand becomes all but impossible outside the most basic of cases. Tools like AWS’s Cloud Formation can go a long way with alleviating some of this pain by providing a DSL to generate repeatable infrastructure from code. Pair this with a Jenkins build server, for example, and you can directly build, test, and deploy Docker containers right to the infrastructure that was generated. Do some pointed research for your use case and be sure to choose one or multiple tools to save yourself a ton of hassle.
Because of the of the deployment model used in microservice architectures, it can be exceedingly difficult to keep track of the health of all the different components in your architecture. Therefore, automated monitoring is essential. Many microservice frameworks like Dropwizard or Spring Boot offer built in analytics and diagnostic features. We recommend that you take advantage of these capabilities for application-level system monitoring when they are readily available. Similarly, we recommend that you utilize any monitoring feature available through your deployment environment. For instance, AWS’s Cloud Watch offers server instance level monitoring and alarm features. These features are indispensable, especially if your microservice system utilizes auto scaling.
There are many positives that can come from using microservices as discussed in our previous article. As we’ve noted in this article, however, the positives are not left without their accompanying challenges. A constantly changing architecture and its respective backing infrastructure forces certain practices to be used that with a standard monolith are not needed. Testing, monitoring, and deployment each take on some new difficulties as we’ve noted. Though there are some challenges that need to be considered, using a microservice-based architecture can still be a very strong design choice for any project when architectural flexibility is of high importance.