Extracting common code to a library seems to be developer’s best practice. Reuse boosts the development, doesn’t it? However, in a microservice architecture shared libraries tightly couples microservices together. You lose a huge benefit of microservices: independence. In this post I like to point out why shared libraries are not a good idea and present alternatives.
In short-term a reusable library speeds up the development, but it leads to coupling between the microservices. Consequently in long-term it slows down the development, increases the coordination effort and hinders modernization. The microservices are not independent any longer. Especially don’t use shared libraries for business logic and models. Instead, accept redundancy, introduce a shared service or reslice your microservices.
“Duplication is better than the wrong abstraction”
RDX in 10 Modern Software Over-Engineering Mistakes
A huge benefit of microservice is independence – independence in terms of development, coordination and technology. This way, we can develop and deploy a microservice independently from other microservices. We don’t have to coordinate our changes and releases with other microservices. This boosts and scales the development in the whole company. However, if two microservices share a library, they are coupled and not independent any longer. Let’s take a closer look at the implications:
- The work on the library has to be coordinated among the microservice teams. This slows down the development.
- It can happen that a change in a library requires subsequent nontrivial changes in all microservices (after updating the library version).
Errors and Side-Effects
- The library will be bloated with features for every microservice that is using it. The microservice gets all functionality and updates of the library even if it doesn’t need it. This can lead to errors and side-effects.
Lost Technology Independence
- With a library comes transitive dependencies. But you only can have one version of a library in your microservices. Hence, the library imposes a certain version of a library which restricts you and may lead to painful version conflicts.
- Besides, libraries come with a certain programming model (synchronous vs asynchronous model, I-call-the-framework-approach vs the-framework-calls-me-approach/hollywood) or libraries (certain dependency injection framework, annotations of a specific library) which is also imposed on the microservice. This restricts the freedom and impedes an optimal design for the microservice.
- Reusing libraries is called “Whitebox-Reuse”. This means, that we often have to understand the internals of the library in order to use it properly. This makes libraries harder to use.
Mental Barrier to Refactoring and Modernization
- I also discovered that developers (including me) tend to avoid changing the shared library. This can be caused by:
- Fear: “I don’t know if this change affects other microservices. Maybe something breaks. I don’t know a lot about the domain of other microservices and can’t estimate the impact of my change.”
- Avoided effort: “In order to update the library, there is a lot to do: change the library, test the library, build on Jenkins, release an artifact, update the library’s version for every microservice, apply further necessary changes to every microservice (this can be nontrivial!), build _every_ microservice, test every microservice… Well, I don’t have enough time and/or desire for this work.”
- If you bypass a necessary change in an upstream library, you’ll inevitably start to hack in order to solve your problem. This degrades the architecture.
- Avoiding necessary refactorings and modernizations will lead to bad solutions and technical dept. This affects the overall maintainability and sustainability of the whole system.
Don’t get me wrong. I’m not dogmatic. Spring, Apache, Lombok, Guava - There is no doubt that open-source libraries are of great value. Modern software development is not possible without them. Usually, open-source libraries have a high level of quality, contain less bugs and are on a level of abstraction that make it reusable for all of us. This is a great difference to in-house-hacked libraries. That’s at least my experience.
It’s okay to have libraries for technical concerns (e.g. for logging or monitoring), because it’s not likely for a business requirement to affect these concerns. But never move business and domain logic to libraries, because this will lead to frequently updates and deployments of all microservices. For instance, if the domain model classes are placed in a library and a new business requirement demands a change in this model we have to update all microservices using this model. Besides, in a microservice architecture, every microservice should be a bounded context anyway (see domain-driven design). Therefore, a microservice should have its own domain model. So there should be no reason for another microservice to use exactly the same domain classes.
Moreover, sharing libraries can also be acceptable, when all microservices utilizing the library are maintained by the same team. This way, the changes only have to be coordinated within one team. And the impact to other microservices can be better assessed, because the team knows a lot about the affected microservices.
- Accept redundancy. It’s ok to have redundancy in order to stay independent.
- Use a shared service. The common functionality can be moved to a new service, which can be developed and deployed independently.
- Reslice your microservices in a way that a shared functionality is not necessary anymore.
Microservices should follow the “shared nothing approach”. This way, we avoid technical and organizational coupling, speed up the development and enable modernization.