One reason is that microservice architecture requires much more operational overhead. So any server we have (except for the database) should be self-contained as a rule, so that it can be autonomous and horizontally scalable.
So far, it is working quite well. You do not need suddenly a Redis for one thing, a ZooKeeper for the next one, and 3 or 4 things to just run the damn binaries. The binaries will start, do whatever migrations need to be done if it applies, and start running. They only need the database. They land health checks, api calls and all the logic needed, in one binary with zero dependencies that is containerized.
This has saved me a lot of pain compared to other architectures where I worked, but those were massive and it was justified (and there was budget for it). But you just need a bunch of teams to be able to do that.
I think going the microservices way for a small team not only does not pay off. I think it can be a suicide.
At the same time, I keep the servers internally modular (enable/disable feature).
So the key here is good engineering leadership to recognize when that point arrives, and push the company to start the transition.
And indeed, microservices solve a organization problem not an engineering problem. When I taught the architecture to new hires, I always say: a service is the largest piece of system that stays together in a reorg :)
And Service Oriented Architecture is the nice middle ground of that infrastructure axis
The history of distributed software is littered with the corpses of attempts to make the RPC look like a function/method call (I worked for a CORBA vendor decades ago). But these two techniques are so different on a fundamental level they are not substitutable except in the most trivial cases. The compute and elapsed time RPC overhead which makes perfectly good local abstractions completely impractical across a network. And then there are the differences in terms of failure modes, concurrency and synchronicity.
So even a beautifully designed and layered piece of “monolithic” software with a code base costing of coherent and clean abstractions will not be easy to port to a microservices/distributed architecture.
It can make sense to have more than one service for purely technical reasons. I once worked on a friend's startup where we built a separate ingress microservice so we could scale it independently from our monolith. There's no organisational benefit to doing so, however. The technical founder and the one engineer are hardly going to block each other.
I am good with that. What we often see is 10 microservices per team of 3 devs. And ideally using the same database.
The team was stuck in a mindset that they just hadn't split things up enough to arrive at nirvana.
I prefer a service that fronts the database. Swap the database, scale it, whatever, and clients keep going.
It's absolutely horrible. Now all the services are tightly coupled without the devs really realizing this. And the side effects of database changes can be super subtle and hard to predict.
- you have a monolith, and it handles normal API traffic patterns
- you have a workload that involves different hardware needs e.g. extremely high throughput relative to the rest of the system (video streaming would be an example), GPUs, high memory requirements with low CPU or vice versa, etc.
Solution is to deploy a service that only handles the specific workload. Whether or not this is a microservice probably depends on your point of view but I'd argue it's at least close.
For example, in microservices if you want some data that another service owns you have to define what it is, write an API, and handle all the fun of network problems. But once it's done you have a nice contract for getting that data.
In a monolith you can just reach over and take what you want. If you know there's a function for getting something you can use it. If you know the instantiated db connection has permission to view a row, just query the db directly. If you want to pipe some data somewhere just add it to the session object and it magically appears where you need it. And so on. You can go so fast! But then someone else does the same thing, and over time your monolith gets slower and slower, and needs more memory, and you find yourself having things on a god object that really shouldn't be there but it's hard to change because the behavior is threaded through everything.
Both of these problems are relatively simpler architectural issues, but it's always preferable to have well-defined complexity over accidental complexity. If you can define how code goes into a monolith and stick with those rules then a monolith is great. Most people can't, and I've never seen a company with multiple teams manage it.
It was a nightmare at that point. We pretty much gave up.
Thing is we were in an illiquid market that didn’t require the kind of scale he envisioned.
Long story short we burnt a hell of a lot of runway building something that was painful to work with compared to the monolith. It did not end well.
Micro services are something that sounds good to the inexperienced but are almost never right for an immature business looking for market fit.
If you really know you can’t scale with just a big box from the outset use something like Elixir or Erlang. Otherwise stick to a modular monolith for as long as you can.
Everything was built/deployed in a single release (unless there was a hot patch, emergency bug fix, in which case we "could" work around it if needed.)