Someday hopefully some genius proposes "hyper-sidecar-ification" where you take microservices and package them together in a sophisticated way to avoid the limitations and latency of an http barrier. As long as it's new & complicated & buzzwordy (even if it's just monorepo again) it can catch on.
> That dumpster fire over there is not my problem
Without having to say:
> I want to rewrite the whole thing from scratch, you'll have to deal with it being down for a few months
IPC (networks, FIFO, pipes or whatever) decoupled services solve problems more on the ops side, from binary compatibility to resource segregation. They do very little on the dev side.
I want to make people do Bart Simpson style writing on the chalkboard “Microservices don’t make things easier” 200 times
1. Create protobuffers (or similar) messages that wrap the requests and responses. E.g. CheckSubscriptionPaidRequest and CheckSubscriptionPaidResponse.
2. Refactor your code so that the fields in the messages defined in #1 are your only mean to pass/retrieve information.
3. If need be, expose the service through GRPC or similar.
4. Repeat for each service/endpoint.
This way, you don't incur in any overhead apart from the negligible creation of the protobuffer messages instances.
Essentially you have:
Modules = Microservices
Wrapper = Terraform/Helm Charts
And practically they work the same way.
This is another thing that used to make sense but no longer does. Back in the day we had pets and not cattle. You couldn't just roll someone's servers and expect everything to be fine. But today we write stateless cattle that can be killed at any moment.
If your concern is code health, a compile-time dependency works great.
If your concern is resource management, then you need a runtime dependency.
The end of the article covers why they felt complete isolation was worth the network costs. Maybe this true for their organization.
From working on one of the largest ruby code base for the last 5+ years, I see the massive benefits of isolation without introducing the http barrier. Yes, service isolation can let you ship faster. In practice, the network barrier will make some kinds of rollbacks easier and other far more difficult.
The reliably of the whole system is a lot more costly with the added network failure modes.
Over time, the proliferation of services impose an ever growing maintenance tax to keep libraries up to date and mitigate security vulnerabilities across an organization.
Tl;dr there are no free lunches.
In this particular case it's worse due to how Rails works. Usually people deploy one Rails thread per core and that core is blocked by the thread.
If that's the case when Service A calls Service B, Service A is blocking a core and waiting until Service B completes. That's effectively doubling resource consumption during that call. You have one server waiting on another server to do work it could have done itself.
This doesn't follow, but everyone always seems to think that it does. They even demonstrate that it doesn't in Phase 1! "Decouple billing logic within the monolith"
It makes the system brittle, slow, and forces strong commitments that dependent services remain up (rolling releases with non breaking migrations, etc).
If I were to do it again, then I would first ensure that the infrastructure is there for inter-service communication to be done asynchronously, and that changes are eventually consistent. Maybe using a workflow manager like Camunda or Temporal. Or even just event choreography between services - either of those is better than a synchronous HTTP call chain of what will become 7 dependent services.
Other then async what would make it less of a “distributed monolith”, can you say?
I guess distributed monolith is a nebulous term, and I'm sure people have their own criteria. To me, the defining characteristic IS the size of that synchronous call chain. If to serve some of the public operations of your system you need to make a synchronous HTTP call that spans more than one service, then I consider those services to be too tightly coupled and the system is closer to being a distributed monolith then a set of independent services (I'd make an exception if the first service is an API gateway or is very explicitly a kind of middleware service, and not defining business logic).
The degree to which the system is a distributed monolith, and how much one should care about that fact or invest effort to steer away from it is a function of how big the biggest one of those call chains is. I don't have a binary definition, more of a sliding scale. The way to avoid sliding more into the direction of a distributed monolith (at least the way I reckon it) is to avoid making those call chains from the get go.
This is the hardest bit, where if the monolith is relying on a shared db transaction between the client and service the network boundary makes them separate. Even without explicit rollbacks, at any high scale/load there can be timeouts/failures leaving an inconsistent data state.
The article suggests migrating a less critical client first, then developing such consistency mechanisms before migrating the more critical clients.
Mainly because depending on another repo can be flakey. If you depend on another repo’s tag i.e. “submodule@v1.3” that tag commit could be changed, which could break the build.
If you depend on another repo’s commit hash i.e. “submodule@hash” then you depend on nobody rebasing or “git push -f” which could remove that hash from the git history.
All these problems disappear if you use a mono-repo, rather than a git submodule…