I'd argue it's cheaper scaling out the monolith or introducing isolated functional silos and scaling them out than even bothering migrating everything to microservices. Also you certainly can't port a complex monolith to microservices; you have to start again. We're quite happily able to shift 15,000 requests/sec from over 2000 http endpoints with a monolith and our kit is at ~20% capacity. Need more? Slice up a silo some more.
Key aspect when you design the monolith is not building a spaghetti monster. If X doesn't need to talk to Y, put a wall there (package level)
> Also you certainly can't port a complex monolith to microservices
Upvoted you, but I disagree with a little. "Microservices" tend to pathological cases, but you 100% can segment a monolithic apps into services. Each module in your application has an interface. Behind that interface, replace it with network calls that fulfill the interface's contracts. (If your monolith doesn't have sections that encapsulate work--well...kinda did it to yourself, but that IME is rare.)
You can, hence my comment about silos. The interface is rarely well defined enough to be able to abstract it over a network boundary from experience. One of the things I see in a lot of monoliths is leaky abstractions and they are really difficult to reign in.
We tend to go for throwing related web front and and API endpoints into the same ball of mud silo and back end that directly with the storage and cache services. We scale those silos up and partition across tenants too. I think a couple of the silos are around the 1 million LoC size now as well.
Fair enough. In places where I've had to deal with that, I was in the code review seat ahead of time and was generally able to go "no no no, let's not do that"--but I can see how stuff can decay.
> Behind that interface, replace it with network calls that fulfill the interface's contracts
Just like that?!
You can encapsulate work fine, without making microservices at all easy to retrofit. There's a big difference between calling a function that does something and returns a result, and calling a function that does nothing until you've gone back to the main loop to handle network input.
(You can confront many of these issues by implementing your services as threads from day 1. Decoupling request and response is a major issue, and this will force you to do that straight away. The other issues associated with moving from single process to multiple processes are fairly minor by comparison.)
> There's a big difference between calling a function that does something and returns a result, and calling a function that does nothing until you've gone back to the main loop to handle network input.
the difference is latency, so of course you put extreme low-latency operations inside the same service.
That. Most folks understand services as separate codebases, running as separate deployments. My take is that they are logical entities to a degree where their physical implementation is irrelevant to their clients.
Key aspect when you design the monolith is not building a spaghetti monster. If X doesn't need to talk to Y, put a wall there (package level)