In the first post of this series on helpful guidelines to help you get started in the creation of effective microservice architectures, I described the importance of effective documentation when developing microservices. In this post, I will tackle the challenge of maintaining consistency in an environment with distributed development teams.
- Lack of documentation will kill your team’s productivity, but selecting the correct way to document is just as important.
- Consistent use of best practices are necessary to maintain developers’ sanity.
- Effective communication is extremely important, but too much communication is a detriment.
- Setting up a good development environment is as important as (or even more important than) the development itself.
Using consistent best practices is an extremely important part of developing microservices. It avoids rework, gets teams on the same page, and creates opportunities for reuse in other projects. These best practices are not always obvious at first. Over the last year, while working on a microservice architecture at a major retailer, we came up with a list of some of them in order to make our lives a bit easier.
The items I’m about to list have to be decided on up-front, and persisted throughout the project lifecycle. These are the most important decisions that you’ll make during the entire project, and will affect everyone involved. We learned this the hard way: during our project, we kicked some of these decisions down the road until they were absolutely inescapable. Since a major amount of functionality had already been written, that meant that a large portion of the code in multiple services had to be retrofitted with these changes. This type of situation, as one may expect, can cause deadlines and budgets to slip.
One of the major things that needs to be decided up front is the framework in which to write the API. The number of modern web frameworks is so vast that you may chose to pick a different one for every microservice that you’re writing. However, in our experience, picking a single framework for all of the services (if possible) is the best solution. A single framework means that you can reuse any configurations, naming conventions, logging systems, metrics gatherings systems, and other pieces of logic which may be applicable to every microservice in the system.
Our Solution: SpringBoot
In the last few years, the Spring framework has earned quite a bit of hype. However, it comes with a set of inconveniences. Somebody used to working with a more modern framework such as Rails or Node.js may scoff at some of the configurations (lots of XML) and wiring that are required to make all of the components of Spring work together. SpringBoot tries to fix this issue by providing a convention-over-configuration approach to framework design. It combines the power of Java and Spring with the ease of use of Rails. Together with Java 8, SpringBoot rivals some more modern frameworks in terms of ease of ease of use and pluggability. Since Java is the world’s most popular language, it should be relatively easy to find experienced developers to fill any open roles. (If you need some convincing that Java is a good language for microservices, take a look at these case studies: Twitter, LinkedIn.)
Having an effective API design is extremely important when building out microservices. This is especially true when using RESTful APIs. The freedom that comes with REST means that every project team has to create its own set of standards. Here is a list of things that needs to be decided when creating your standards:
- Error responses: How will your API behave during error conditions such as unavailable resource (404), internal server errors (500), or downstream service errors? These responses have to be universal to each of the microservices so that the error handling logic for them can also be moved to a common library.
- Paging: What will be the limit of objects contained in a single http response? What will happen when the response to a request yields a result that contains a large list of objects? These questions will be decided by your paging strategy. GitHub has created a very good API paging convention that can be used for any microservice: GitHub Paging.
- Naming conventions: There will most likely be an overlap in terms of which which data each of the APIs handles. This means that the naming conventions for any and all of the parameters have to be decided up front, and shared among teams.
- Search convention: When providing a search endpoint, how will the request be structured? There are a lot of ways to do search in REST. However, I would suggest using simple query-string parameters instead of any over-engineered approach with subqueries. This StackOverflow response echoes our sentiment.
As trivial as it may sound, naming can become a huge headache if not done properly. Naming conventions for services, API parameters, and systems have to be decided up front, persisted throughout the project, and documented in your low-level documentation. If the names of things have to change throughout the project, every dependent system needs to be changed. Renaming things is a relatively simple task, but it is also annoying busy work that can be eliminated if you follow this advice.
Testing and Test Coverage
Testing microservices can be very tricky. There are some tools that may be very helpful in such a task, however. Moco is one such tool. It allows you to mimic services as if they were running on your machine at a specific port number. This allows you to effectively test a piece of code that is dependent on another service. Some teams have an arbitrary test coverage requirement (80% is popular), but when dealing with production enterprise software, the coverage should be 100%—or even more, as some components might be called in different tests. You should have unit, integration, functional, and performance tests for each piece of functionality in a microservice. Martin Fowler’s deck has some good guidelines for microservice testing.
Best practices are a core part of developing microservice architectures. They set a standard for your team to follow, and give structure to a process which could otherwise get out of hand, given its distributed nature.
In the next post, I will cover communication strategies we have found to be useful when developing microservices.