This guide helps answer three questions: What is a microservice, Why might your situation benefit from one, and How do you actually create one, starting from scratch?
The term microservice has become a buzzword. It has been thrown around and used in so many contexts that it can be hard to narrow down what it represents anymore. However, I've noticed at least instance where the term is used consistently, which I will repeat here.
A microservice is an independent component of an application designed to do one thing. Typically, different microservices work in groups over HTTP APIs and function as one service. Microservice are generally
- Designed to do one thing
- Independently scalable, deployable, and testable
- Easy to maintain
- Owned by one team
Why does this matter? When an application is first built, it is often created by one software team. As customer usage increases, so do feature requests and performance optimizations, and eventually other teams begin building out the program as well. After a year of this, the codebase has grown so large that nobody completely understands it, making it difficult to modify. This can also make the application slow and unscalable and it only gets worse as time goes on.
When you find yourself in this situation, consider breaking up that monolithic application into several microservices. It is simply easier to understand, improve, and build on codebases when they are small. Each microservice has their own set of dependencies and can be managed as their own cluster, improving maintainability. Logging and metrics become simpler and mean time to find and fix bugs in production can be drastically reduced. All-in-all, the work taken to build out microservices may be well worth your time.
Building a Microservice takes time and requires a lot more than just coding. But in my time as a software engineer, I’ve noticed that the process typically follows a couple of steps.
Define the one service your microservice will provide. Perhaps it will handles version control history for a database. Maybe it provides an API to train a neural network. Point is, if you want to avoid breaking up the microservice into parts again further down the road, choose only one thing for the service to do.
Create a design document. Documenting your proposed design is crucial, as everyone helping to review, build, or maintain the service will need to understand how it works as well. The design should at minimum include
- A proposed tech stack - are you using Golang with Kubernetes or Rust with Nomad, and why?
- The architecture - how do all of the pieces fit together?
- Predicted performance metrics - how will the microservice perform better than the existing application and by how much?
- Alternatives - what are the other options and why are (or aren't) they viable?
Bring in other engineers for design review. Design discussions and feedback will help you recognize problems before they become bugs in the implementation. In this step, many alternatives are considered and you may end up rewriting your design doc at least once. For my latest microservice, there were at least 3 formal discussions with 8+ engineers which resulted in significant design changes. But we learned a lot and avoided what could have been very hairy problems. It might seem easy to plow ahead with your idea, but taking the time to thouroughly review it will likely result in a better, more maintainable product.
Create milestones and tasks. While microservices are designed to be small, they clearly take time to build. Splitting up the project into small pieces makes it easy for several people to be involved and for tracking progress. This also makes it easy to estimate how long each task will take, add major milestones and give an estimate for project duration. JIRA or Kanban boards are great for tracking individual tasks, but I also keep these tasks and milestones in a Google Spreadsheet to have a focused view on the microservice development.
Build a Proof of Concept (POC) to validate predicted performance metrics. This step could be considered optional as I haven't needed to do this for small services; my POC effectively evolves into the final product. But when you have a large project ahead that could consume many dev hours, it might be worth building a simple POC to test your design at a high level. If it performs as expected under stress, this can give you and your managers the confidence that the real system will only do better (and if it doesn't, you can go back to the drawing board without feeling like you wasted too much time). This step is often completed in conjunction with step 3.
Code away! If you've made it this far, you're already doing great. Remember to use a version control system like Git, frequent code reviews, and unit/integration testing!
Test your deployment with real data. Once your build scripts are written and the program can run in the wild, you need to test it against its monolithic predecessor. Do the predicted performance measures and POC evaluation live up to reality? If not, what's causing the discrepancy? To accomplish this, I usually duplicate reqeuests being sent into the original application and route them to the microservice cluster. Testing with real traffic has the added advantage of you knowing almost exactly how your service will behave on its own and will help you eliminate many bugs before they can cause harm.
Gradually transition to the microservice. Once your microservice has been designed, implemented, and thorougly tested in data centers, you can start directing traffic to it and it alone. It may be appropriate to do this all at once with a one-line update to a config file, but I've commonly seen engineers start with 1% of traffic, then 5%, and so on. This provides one last chance to catch any bugs before they can cause problems.
I gave these steps an order, but there will be crossover. While this is tuned towards building a microservice to replace an older, large application, the principles could also be applied elsewhere.