Weighing the Pros and Cons of Microservices

Background

During the first part of my pairing tour at 8th Light, I had a chance to code with Eric M. We worked on a Backbone and Coffeescript app for his client; our task was to create a set of survey questions on the side of a webpage that the user could follow to determine the appropriate next steps for a claim. This was my first time diving into a large codebase, and I learned a lot. Some of the topics to which I was exposed were:

  1. How do you navigate a large codebase? When the size of the codebase gets too large, you cannot effectively navigate it without some sophisticated tooling.
  2. How do you follow function calls back to the source of a bug? Up until now, the largest codebase that I had ever worked with was my Java Server. Finding the source of a bug in a large codebase is much more difficult.
  3. How do you methodically eliminate expired code from a codebase? The codebase had a lot of old Marionette code that needed to be eliminated. However, it was not valuable to the customer to create a story just to refactor code. Thus, how can you refactor old code while completing new stories?
  4. How do you collaborate with teammates that are from different companies, working on different parts of the system? There was a lot of communication between developers to make sure that nobody’s commits conflicted.

On the second day of pairing with Eric M, I sat in on an IPM and retro with the entire team. The retro was particularly interesting to me because it was a discussion about microservices at the company. After the retro I dug a bit deeper into microservices myself to get a better idea of how they work. Below is a general overview of my findings.

What Are Microservices?

Microservices are an architectural design that breaks up a large software project into small, independent, and loosely coupled modules. Each individual module is responsible for a single, focused task and can only communicate with other services through a simple API. They can be thought of as a specialization of service oriented architecture (SOA).

A single microservice has a few main properties. Specifically:

  1. It conducts a single business activity with a well-defined outcome.
  2. It is completely self-contained.
  3. The consumers of the microservice have no knowledge of how it works.
  4. It can leverage other services through their respective APIs to accomplish its own tasks.

Due to the isolated nature of microservices, they are relatively easy to change when compared with traditional monolithic applications. A microservice can easily evolve through continuous refactoring without the need for any big, up-front design.

Advantages of Microservices

Microservices have gained popularity over the past few years for good reason. They offer a number of benefits over traditional monolithic or SOA apps.

Each Service Has a Single Responsibility

A well designed microservices does one thing very well. This is a huge boon for the developer for a number of reasons. First, it is far easier to reason about a codebase that does one thing rather than a codebase that does many things. This increases the ease at which new features can be developed. Second, you can optimize your codebase to do the one thing extremely well. You might not be able to do that in a monolithic application since you need to balance the varied responsibilities in the different parts of the code. Finally, systems that are built in this way can be loosely coupled, leading to exceptional code re-use.

Developers Can Work Independently of Each Other

In a monolithic app, developers need to be very careful not to work in the same part of the codebase at the same time. Otherwise, the changes will overlap and a large amount of time needs to be spent reviewing the commits and merging them appropriately. Conversely, multiple developers can work independently of each other in the microservices architecture – as long as they are working on different microservices. This leads to truly parallel development. A developer working on microservice A is not constrained by microservice B in any way (aside from the API provided by B). Additionally, developers can use different tools and languages in different microservices as long as the services pass information between themselves with the explicit APIs.

Smaller Codebases

As an application grows in functionality, the size of the code base grows with it. This is not a simple, linear relationship. Rather, developers know that large codebases can be exponentially more difficult to manage than smaller, leaner programs. Since microservices are only responsible for one thing, an individual service is relatively small and easy to understand when compared to a monolithic app.

Not only is the codebase smaller, but also the testing suite that accompanies the codebase is smaller as well. Smaller codebases generally need fewer tests. These test suites are much easier to interpret (i.e. it is much easier to read through the output from 100 tests rather than 1000) and they can run much faster. This makes it easier to use techniques such as TDD.

Disadvantages of Microservices

Microservices are not a silver bullet that will magically solve all problems. There are a lot of things that need to be taken into account when building a microservice architecture.

Shifting the Origin of Complexity

Complexity does not disappear in microservice architecture. Instead, it changes from the complexity of managing a large codebase to the complexity of coordinating a number of interacting components. Step-by-step imperative programs suddenly need to be re-written as inherently asynchronous pieces of code. Operations has to ensure that dozens of processes stay running rather than just one program on one server. Developers need to think about collecting data from machines that might span the globe. Depending on the task at hand, a traditional monolithic application might actually serve the client better than a microservices architecture.

 

Testing the System Can Be Exceedingly Difficult

Testing an individual microservice is simpler than testing a large app with many moving parts. Since a microservice is designed to do one thing very well, the tests for that microservice tend to be very focused and cohesive. However, testing a vertical slice of functionality across many different microservices can be extremely difficult due to the fact that ideal microservices only communicate through rigid APIs.

For example, suppose an error occurs within a microservice. This error propagates outward and affects a few other services over some time period. All of the services reliably record the details of the error in their logs, complete with API endpoints hit and timestamps. A developer steps in to analyze the log files and fix the potential bug in the system. There are many things to consider:

  • How does the developer collect the log files? Are there dozens of different systems where log data needs to be accessed? Does the developer even have permission to access the log files on each microservice?
  • Once the files are collected, how does he compile them into one coherent report? How does he analyze cause and effect between services (A caused B which caused C)? Even worse, how does he analyze potentially cyclic calls between services (A calls B which calls A again)?
  • Once the logs have been compiled, how can he go searching for the bug? Does he even have permission to alter the code on all of the microservices in question? If he does, how can he write a failing test to isolate the bug?
  • Once the bug is found, who is in charge of fixing and maintaining the code? Is it the initial developer who started the analysis? Is it the team in charge of the code?

 

Coordinating Data Can Be Very Difficult

Microservices are designed to do one task very well. Ideally, they should be completely unaware of the overarching architecture of the app and should never have to probe the internals of another microservice for data. However, you can run into problems when two services need to access the same piece of data.

Suppose you have a “user account” microservice and a “payment” microservice. The “user account” service handles user login, personal information, and certain profile settings. The “payment” microservice involves payment processing and keeping financial records (e.g. receipts, invoices, etc.). Both of those services probably require the user’s email address to function properly. Which service owns that piece of data? If they each have a separate copy of the email address, do you need another microservice to sync them up periodically? Do you need a third, separate microservice to store all of the shared data? It can be difficult to determine the domain of each piece of data in your system.

Microservices are a Relatively New Idea

The idea of coordinating many services is not new in the realm of software design. However, the scale at which services are used in microservice architectures is very new. Dozens of isolated services can communicate amongst each other to solve problems on a global scale.

Currently, there are not many frameworks or tooling solutions to manage a large microservices system. Thus, a team that wants to build a set of microservices will also need to invest a large amount of time into building a structure through which these services can be coordinated. Additionally, there are few “experts” in the field of microservices – nobody has been working with microservices for very long, so the members of the community are still learning from each other.

Reflecting on the Retro

First and foremost, I want to say that this was an excellent retro. I have now sat in on a few retros for both my own apprenticeship and for client projects; this retro stayed on topic and deeply probed the value provided by the microservices architecture. I can’t speak for the other members of the group, but I thought that the discussion was very productive for a few reasons.

  1. The group discussed the pros and cons of the microservices (e.g. deployment, testing, architecture, etc.).
  2. The group discussed how microservices affected other teams in the company.
  3. The group discussed potential designs for the future organization of data.

My only critique was that the group should have created an actionable plan of development by the end of the meeting. I thought that the team did an excellent job outlining the current state of the services, but they did not put enough thought into the system moving forward. In their defense, they wanted to reflect on the comments made at the meeting and then re-evaluate the state of the system later. However, I am not sure if the re-evaluation ever happened.

Based on my reading and what I saw at the retro, I would organize the discussion a little bit differently. During the first year of developing a microservice architecture, I would schedule a retro every month. That is not to say that every member of every team is going to be attending an additional monthly meeting. Rather, every team would send one “representative” to attend the monthly retro, and the teams would rotate representatives. Ideally, an individual developer would attend two retros over the course of the year with different groups.

I would involve a minimum of two, and ideally three, teams per retro and would restrict attendance to a maximum of six people so that there would not be too many voices at the meeting. Each meeting would follow the classic retro progression (i.e. what went well, what did not go well, what was neutral), but additional time would be allotted to discuss concrete plans for future development.

Why this extra overhead? The success of microservices hinges on cooperation – both between the isolated services and the teams that build them. A discussion about microservices is inherently incomplete without input from all of the interacting pieces (or at the very least, a subset of the pieces). Thus, these retros ensure that every team is aware of the work done by the others while simultaneously keeping meeting overhead to a minimum. After the first year of development, I would reduce the number of retros down to a normal schedule (I have seen most clients do them quarterly).

References

Badola, Vineet. Microservices architecture: advantages and drawbacks. http://cloudacademy.com/blog/microservices-architecture-challenge-advantage-drawback/. Accessed on 24 February 2017.

Clayton, Richard. Failing at Microservices. https://rclayton.silvrback.com/failing-at-microservices. Accessed on 24 February 2017.

Devlin, Mac. Error Tracing and Routing in Microservice Architectures. http://rockthecode.io/blog/1202/. Accessed on 23 February 2017.

Lewis, James and Fowler, Martin. Microservices. https://martinfowler.com/articles/microservices.html. Accessed on 23 February 2017.

Mauro, Tony. Adopting Microservices at Netflix: Lessons for Architectural Design. https://www.nginx.com/blog/microservices-at-netflix-architectural-best-practices/. Accessed on 27 February 2017.

Microsoft Developer Network. Chapter 1: Service Oriented Architecture (SOA). https://msdn.microsoft.com/en-us/library/bb833022.aspx. Accessed on 27 February 2017.

Wootton, Benjamin. Microservices – Not a Free Lunch! http://highscalability.com/blog/2014/4/8/microservices-not-a-free-lunch.html. Accessed on 27 February 2017.

Leave a comment