In part I of my four part blog series on Microservices, I explained what microservices are and the benefits you will see by adopting this architecture.
However, life is all about tradeoffs. In part II of this series, I will go over the things you need to consider while moving to microservices, as well as some challenges that crop up even when you do everything right.
Microservices for greenfield projects
Anytime your team develops a new application from scratch, it feels great not to inherit technical debt and be locked into outdated decisions made years ago. Most teams developing new apps today would probably choose to containerize them using Docker and adopt microservices architecture for speed and agility.
However, there are a few things to consider before you start:
1. Degree of independence
The first decision is - how independent do you want your services to be? You can choose one of the following options:
- Each service is completely independent with its own Database and UI. This is consistent with an extreme microservices architecture where services really share nothing and are completely decoupled. The nice thing is that the team for each microservice can choose the database that best addresses their requirements. However, this approach makes it much harder to ensure that all data stores are in sync and consistent. For example, you need to verify that the same userIDs exist in all data stores, and there is nothing missing in any of them. Also, database management tasks like backups need to replicated by each team.
- You can choose to share some components, usually your Database. This makes it easier to enforce standards across all teams and ensure data consistency across all services. However, this also means your services are not completely decoupled and if someone updated a table or schema, other services might be affected as well.
Both approaches have pros and cons so you need to decide what you can live with. At Shippable, we chose the second approach since we did not want to deal with inconsistent data and then spend time finding ways to ensure consistency.
2. Code organization
There are several ways you can organize your codebase. You can create a repository for each service, you can create a single ‘mono repo’ for all services and have a folder for each service. The pros and cons of both approaches were discussed in our blog post on 'Our journey to microservices: mono repo vs multiple repositories' where we explained why we chose the mono repo approach.
3. Technology stack
It is difficult enough to decide on the technology stack for a monolithic application, but now this decision has to made for every microservice. What seems attractive in theory can become problematic in practice if your services are too heterogeneous. Standardization becomes a problem and there is potential for cowboy behavior. Also, it's harder for people to move between teams if every team is using completely different stack.
We recommend a balanced approach where there is a preferred technology stack across the application. If any team wants to override this default stack, they should justify their decision with pros and cons of why a different stack is more suitable for their microservice. Your technology stack should include programming language, testing and logging framework, cloud provider, infratructure, storage, monitoring, etc.
4. Operational complexity
Microservices greatly increase operational complexity since you need to rethink operations from a very fundamental standpoint. You need to consider the following aspects:
- Infrastructure: Defining infrastructure requirements for microservices and then provisioning and maintaining infra for each microservice adds a degree of complexity that most Ops engineers working on monoliths are not accustomed to. Plus, as these services are scaled up and down, infrastructure needs to be provisioned and brought down automatically so you need a very sophisticated level of automation.
- Load balancing and scaling: You will likely need a scaling strategy that is much more complicated than for monolithic applications, which are always scaled out (x-axis). With microservices, you will need to figure out if you need to scale all services or just a subset when there is a spike in demand. Your Ops team will need to understand y axis scaling since the microservices approach is consistent with it and z axis scaling to get the benefits of x and y. More on the scalability cube here.
- Service discovery: The set of service instances in a microservices world changes dynamically due to scaling and upgrades. Also, services have dynamic network locations, so you need a way for new service instances to be discovered. We recommend a service registry like Consul for this since it has worked very well for us. Read more on client-side service discovery, server-side service discovery and a list of commonly used Service Registries.
- Monitoring: This has to be configured and maintained for every microservice, increasing the complexity for Ops engineer(s). Also, the monitoring solution has to handle scenarios where a subset of services are scaled up and down.
The operational complexity itself should give you some pause before you decide to move to microservices. Unless you are aware of the challenges with microservices and have a plan to address them, it will be a painful transition.
5. Continuous Delivery
Being able to swiftly deploy small independent units is a great boon for development, but it puts additional strain on operations as half-a-dozen applications now turn into hundreds of little microservices. Many organizations will find the difficulty of handling such a swarm of rapidly changing tools to be prohibitive.
This reinforces the important role of continuous delivery. While continuous delivery is a valuable skill for monoliths, one that's almost always worth the effort to get, it becomes essential for a serious microservices setup.
Organizations that evangelize microservices, like Netflix and Amazon have the resources to build homegrown custom continuous delivery pipelines for their microservices. However, not every organization can afford to do that. Even if you can afford it, you should think about whether your time is best spent building fragile homegrown automation that has to be custom for each microservice, or whether you want to improve your own product instead. You have three choices:
- Decide that you do not want to pay the microservice premium and stay with a monolithic architecture
- Bite the bullet and build homegrown pipelines by cobbling together several different tools that help with parts of the workflow. The problem with this approach is that as the number of microservices increases, the time and effort required to automate them increases at an even faster rate
- Use a CD automation platform like Shippable that will get you 90%+ of the way towards complete continuous delivery for heterogeneous microservices.
I will publish a blog post on the third option in the next few days as part III of this blog series that will also show how Shippable can help with Continuous Delivery as well as infrastructure automation, scaling, and service discovery.
6. Team organization
Last but not the least, you will need to reorganize your team(s) to ensure that every microservice is really developed, deployed, and maintained independently. You can't have your engineers working on multiple microservices since that will invariably lead to decisions that optimize for factors that don't correlate to what's best for each microserivce.
An independent, self-contained team should work on each microservice. Amazon's 2 pizza team approach gives a ballpark team size - it should be small enough that everyone on the team can be fed with 2 pizzas for dinner, i.e. less than 10 people. Each team should be balanced with expertise across Dev, Test, Ops, DB administration, UX, and even product management in some cases. This doesn't mean you need a unique team member for each role, just that they should be addressed within the team For example, a DevOps engineer satisfies dual roles of Dev and Ops. Similarly, each team can also have a manager who does writes specs and defines the UX.
There are variations of the approach above, such as centralizing the Ops, Product Management, or Platform teams. These articles sheds further light on the ways teams can be organized.
- Adopting microservices at Netflix: Lessons for team and process design
- Want to develop great Microservices? Reorganize your team
Microservices for existing projects
If you want to move your existing monoliths to microservices, you will still need to consider all the points described above. However, there is an additional challenge specific to your case. You will need a strategy that helps you make the transition in stages.
7. Strategy to transform from a monolithic architecture to microservices
This is an extensive topic and deserves its own blog post. Just like Rome wasn't built in a day, your transition will take time and dedication. In a nutshell, you need to do the following:
- Implement continuous delivery so that you have the right automation in place as the number of services starts growing
- Move to a distributed source control system like GitHub so that teams can work independently without stepping on each other
- Dockerize your application to get portability and the ability to spin services up and down within seconds
- Always build new functionality as a microservice
- Convert existing components gradually, starting from the least complex business functions with fewest dependencies and working your way to more complex functions.
As you can see, adopting microservices is not trivial and should be done only if you see enough value for your applications. I will elaborate further on Continuous Delivery for microservices and strategies for moving existing monoliths to microservices in upcoming posts this month.