One of the advantages of Microservices architecture is that it enables components to have deployment independence. Based on my consulting and software development experience deployment independence is often overlooked and very few teams achieve it. Deployment independence is important since it brings true agility and reduces communication overhead between different teams and services.
Shared libraries make Microservices tightly coupled and introduce hard dependencies. Since, now a team making a change has to ensure that it does not break another service that depends on the shared library. This requires communication between multiple teams. Also, change in a shared library leads to all the services that depend on it to be redeployed. This leads to long build, release, and deployment times. We might have to consider the deployment order of services as well. All this leads to more synchronization and communication between teams. So, it is recommended that in Microservices architecture teams should avoid using shared libraries.
I also principally agree with the above mentioned thought process. There are a few exceptions where I still prefer to use shared libraries. These are:
- You want to reuse a critical technical logic/concern that can’t have its own independent life cycle. They typically don’t change depending on the business need. Let’s take a couple of examples to understand this point.
- an HTTP client that you use to talk to core services. Http library knows which operations are idempotent, adds idempotency key when required, adds retry logic with exponential backoff and jitter for idempotent operations, adds sensible timeouts, does exception translation, etc. I don’t want all my services to duplicate this logic. It is better to abstract this logic in a shared library which is maintained by the platform or shared libraries team.
- I prefer to use a library that all services use to return success and error response in a consistent format and structure. During the initial phase of the software development you might see changes happening to such libraries often but after the initial phase they stabilize and changes do not happen that often.
- An extensible validation library.
- You want to write an eloquent API over a complex/confusing/poorly written/boilerplate friendly/low level library. In the last assignment I wrote an abstraction over Apache POI to make it easy for my team to use it for our use cases.
- You want the freedom to change third party API in future but don’t want to pay the cost of pass-through service. These days the systems we built use many third party APIs. One of the neobank I worked at used a third party accounts and payments infrastructure API. One way to abstract your system from the third party API is to use service wrapper or pass through service. There is a network and performance cost to adding a pass-through API as you are adding a network hop. Another way is to wrap the third party API client in your custom library. This way you still have the freedom to tomorrow change the underlying third party API. It is like applying a strangler pattern.
Software design is all about giving you an option to change and evolve your system in future. Shared libraries if done well and for the right reasons do not introduce hard dependencies that we want to avoid in Microservices architecture. Instead, extracting out such common code into shared libraries can speed up development of new services.