> The same exact results can be yielded by OOP interfaces and implementations, so I unfortunately don't feel this is a valid argument for microservices.

I'm not sure about this, there's so much going on in most software, a lot of the ruin comes from the dependencies around your code, like libraries and frameworks. Let's take web development as an example.

Suppose you need to query the database in some legacy project - well, it doesn't have a framework that supports proper logging of each query/parameter for debugging, it doesn't have any codegen capabilities, it has a wonky connection pool implementation. And yet, if you want to work within the same codebase, you have to use it. What could take a bit of codegen and an hour of work now can take close to a day, all because you're pigeonholed into using what's already there for "consistency".

Suppose instead you now want to have a task queue. Well, instead of RabbitMQ or something of the like being used, instead you have to work with what's present - a bunch of pre-existing abstractions and a DB centric implementation (with arguably poor documentation) that the business requirements have largely outgrown, so now you'll need to hack around the existing code, hope that you break nothing there, all while attempting to create something passable (which may or may not be viable with this approach, depending on the use case).

And doing logging? Well, maybe the team didn't do structured logging or log shipping, so you have to be very frugal with the logs, or spend time setting up Logrotate or an alternative for that monolithic app, just so you can turn on some additional logging and make sure that the eventual rollout actually works.

Well, what about the actual database or maybe tests? Be prepared that the test framework will be somewhat out of date, some of the test utilities won't quite fit what you'll try to do and perhaps your current ORM and DB choices won't really work well with the idea of letting integration tests test actual DB statements and migrations against a DB instance that would be setup for the tests and torn down automatically afterwards (say, a PostgreSQL/MariaDB container somewhere, as opposed to MS SQL or Oracle), so you won't be able to test every schema migration that easily.

You might even run into a situation where you need to run a relatively recent runtime (say JDK 11) but the project is stuck on an older release (JDK 8), whereas migrating everything isn't going to be done for now. So you can't do what you need now, because the libraries don't support your runtime. Worse yet if the monolithic project has dozens of business use cases, whereas the bit of code that's preventing the migration is needed for just one or two of them.

I won't even get into instrumentation and scaling parts of the total solution up or down, or things like configuration, since that'd make this needlessly long.

I think that microservices indeed are a good choice for mitigating some of these issues, by untying your hands - the impact of issues is lessened, to a per-service basis. An individual service might therefore rot and this rot could be addressed separately, without slowing everything else down. Even to the point where the old implementation can be thrown out and rewritten, if that's ever needed.

However, microservices come at a possibly huge operational and complexity cost otherwise, to the point where they might either make the project late, hard to work with, or unfeasible altogether. And it's hard to know which approach will be the best for any given situation. Starting out with monoliths makes sense, because most smaller projects will never need to be split up, but when you need to do that, you will really need to. Modular monoliths (e.g. feature flags for enabled/disabled bits of functionality) can be an intermediate step.

In an ideal world, programming language module systems would be enough. We don't live in a perfect world.