Microservices
Opened this issue · 3 comments
10 Challenges in Adopting and Implementing Microservices
We always hear great things about Microservices. But today let's talk about the top 10 challenges that come with adopting Microservices.
Managing Microservices
As the number of microservices increases, managing them becomes tough. If there is no plan or accountability then we might end up with a lot of tiny microservices or with a huge macro-service.
Extensive Monitoring and Logging
Monitoring what happens across the entire infra is critical. Along with this, we would also need an ability to trace end-user request path spanning services - also called Distributed Tracing.
Service Discovery
It does not take much time for our services to grow beyond 100 and at that scale, discovering a service becomes a pain requiring us to put Service Discovery.
3 ways to do it are
- a central service registry
- load balancer-based discovery
- pure service mesh implementation
Authentication and Authorization
Inter-service communication should be secure to ensure that a service does not abuse others; hence we need to put auth in place that allows authorized services to talk to each other.
Config Management
Every microservice has a set of configs, like DB passwords, and API Keys. Committing them to the repository is an unacceptable practice, and we would not want every service to have its own config server.
Hence we need to have a central config management system that is fault-tolerant, robust, and scales well.
No going back
It is extremely difficult to move back to monolith after the teams have tasted microservices. A few reasons would be
- services are written in various languages
- teams used to being autonomous
- teams have adopted new tools and processes
Fault Tolerance
Outages are inevitable and as engineers, we always try to minimize them. A way to achieve this is to keep services loosely coupled that keep outage isolated ensuring no cascading failures.
Internal and External Testing
End-to-end testing becomes complex as it is hard to spin up environments with all services running fine.
Design with Failures in mind
Robust microservices require a counter-intuitive approach, and we need to assume everything would collapse after every line of code. Then we amend the code and architecture to handle it and re-iterate.
Dependency Management is a nightmare
Managing dependencies across services is tough and it leads to a slowdown. The 3 kinds of dependency to be careful about
- sync dependency on other services
- services sharing a common library
- service depending on data coming from other services
Things to remember while building Microservices
Microservices are much more than an engineering problem to solve and let's look at some key engineering and non-engineering things to remember while building them.
Growth Opportunity
Microservices are a great growth opportunity given there are so many engineering and non-engineering challenges to solve. Every engineer or early leader should grab this opportunity to showcase his/her prowess and earn leadership brownie points.
Conflicts are inevitable
Engineers are passionate, and intra-team and inter-team conflicts are inevitable. But the conflicts should be gracefully by
- taking data-driven informed decisions
- consulting senior engineers
- sometimes moving on without creating a fuss
Architecture Evolves
Vision evolves and so would our architecture. The decisions we took while building it might not be the best today and hence we should be okay with the code getting scrapped and seeing some dramatic changes in the flow.
Technical Debt
We cannot always build a Microservice in the best way possible. Engineering teams are always running against time and aim at delivering things quicker.
This requires us to cut some corners and make some inefficient decisions and this is called Technical Debt. Over time such debt piles up and reduces the development velocity.
It is important to clean up technical debt periodically, by reserving ~10% of the bandwidth of every engineer every sprint.
Enforcing Standardizations
Standardizations are essential for microservices as it provides a clear set of guidelines to use for building them.
To ensure that every team is not building and setting up their conventions from scratch, we create a Template that everyone can use and build on top of it. This aligns with the DRY principle ensuring we do not waste time doing repeated things.
Some engineers and teams might see standardization as strangling, and hence we might see a potential backlash. In order to ensure this is adopted smoothly
- we should have a forum that decides the standards instead of a central team
- the forum should have proper reasoning behind every decision taken for the template
This would help us keep such templates and best practices inclusive while ensuring a positive sentiment all around.
Business over Engineering
This is offensive, but it is true. Engineering exists because the business does and hence while building microservices we should always remain aligned with the strategic goals.
For example, if the business priority is profitability we should not have over-provisioned microservices that leak money.
How microservices should expose their API interfaces
Running Microservices in isolation does not make any sense; it is natural for them to work together and solve a bigger problem. This would require each microservice to expose a well-defined API interface simplifying others to talk to it.
The following are the best practices that we should follow that would make interfacing and integration easy.
Forward and Backward Compatability
While rolling out any changes in a microservice, we need to ensure they are both forward and backward compatible. If not, it would break the consumers or interfacing microservices.
Three key places where we need to be extra careful are
- while changing the database schema
- while changing the API response body
- while changing the message format in async communication
We can ensure forward and backward compatibility if we
- never abruptly delete any column/attribute
- never abruptly change the type of the column/attribute
We should always roll out breaking changes in phases ensuring the dependent services remain unaffected.
Make APIs tech-agnostic
Tech evolves quickly in the world of software engineering, and hence we would always feel like using the new shiny thing available. While having that urge, we should always ensure we are not picking the technology that would induce tight coupling.
For example, we should not pick a framework that would require the interfacing services to be written in a particular language or require them to use a specific tech stack. This would take away autonomy from the interfacing services as we are dictating which stack to use.
Dead simple consumption
Microservices are built to interact with other services and get things done. So, the core focus should be to make things super simple for anyone to integrate.
It does not matter how good your LLD is if the API interface is hard to integrate. Be consumer-centric while designing the interface of a microservice and ensure you have
- simple API
- simple data format
- use common protocols
Hide internal implementation details
Never let other microservice learn about the internal implementation detail of your service. If they interact using internal details this would create a tight coupling between the two services.
Internal details could be
- broker for internal communication
- building dependency on transitive dependencies
- allowing directly connecting to the private database
It is always safe to hide the internal implementation details and expose a strict interface to interact with the service. The interface could be REST, gRPC, or anything that your org uses.