That is a big red flag. Microservices that suffer from shared code changes are not really microservices, but a distributed monolith instead.
That is a big red flag. Microservices that suffer from shared code changes are not really microservices, but a distributed monolith instead.
If you change code that other services are using, you can break those other services. No way round that.
The only real difference is that services have a slow serialized network interface that fails 4 or 5 orders of magnitude more often than libraries, but can migrate over memory domains.
I generally avoid creating shared libraries, they're a trap. They have a very narrow band of usefulness squeezed in between the more palatable solutions of creating new services or just copy & pasting code and allowing it to diverge for each different use-case.
For example: let's say you have lots of integrations, and you need to scale compute, and parse and generate common data sent to and from the integrations.
You can either have a monolithic integration service which you scale out on load; or you can have integration-specific services that scale out on load and share your data parsing & generation library. Due to multiple concerns, there's no "best slice".
FWIW, scaling out compute is a stronger argument to me for a service boundary than responsibility segregation. Scaling out requires distribution; scaling up complexity doesn't, though it can help for other reasons, like CI/CD. I prefer FaaS architectural patterns with the freedom to share libraries in different functions (images) to services, especially if long-running state is not needed.
Having a shared library is not a bad thing on its own. Making the library a bottleneck is the anti-pattern.
If you wish to have a shared-library of microservices you should be prepared to have multiple versions of it running at the same time without any pressure to update everything at once.
If your shared library is the bottleneck, it means that your microservices are tighly coupled (hence the distributed monolith)
What does that mean?
One common example is, instead of having a shared library that reads & verifies JWTs, use a gateway service that handles this before requests reach the upstream service.
This means changes to your organization's JWT code will only require a redeployment of one service, the JWT Auth service.
Microservices using shared libraries -> ok
Microservices "suffering" from shared libraries -> not ok.
They might well all share the same basic framework code, of course. Why not share code for recurring concerns like auth?
If you don't want to do that, then you need a simple shared library for that.
The problem is that there is no easy way to draw the line between "this is obviously a trivial library function we should just link into our code" and "this is something we can't share because it would create friction or break our isolation".
Auth is obviously a "service" but phone number formatting as a service seems extreme.
You clearly haven't finished drinking your Kool-Aid yet.
The auth service will likely have to hit a DB anyways. Assuming the microservice call has roughly the same network latency as the DB call and the DB has 0 response time, it would double the total time to perform the auth. It only gets more favorable as DB response times go up.
More generally, I think microservices make sense in scenarios where the time to process the request is longer than the network latency incurred by making it a microservice. Things that have to hit a DB are generally okay. Pure functional things that just compute on CPU and RAM are generally not, unless they're very computationally expensive like running a simulation or something like that.
So lets say we have shared code for doing something like phone formatting. My question for more experienced microservice practitioners is -- does it make sense to create a microservice for preprocessing data in general? Phone number-formating-as-a-service is excessive but creating a microservice for processing data where phone number formatting is just one aspect of this service makes sense to me. All other services can throw data at the data processing service and get data back in some sort of standard and expected way conforming to whatever business logic/processing rules required.
Shared code shackles everything together, like global variables...
In other words, if you can share a lot of code between services, a monolith is actually an appropriate architecture.