Team being the keyword here. If you have 3-5 developers per microservice, you're absolutely okay. If you have 3-5 microservices per developer, that's when it gets ugly.
If "any developer" can do it, then the problem is not with monoliths, but rather with your pull request reviews process and lack of code ownership. This can even be helped by Github and other platforms with a CODEOWNERS file.
Microservices by themselves doesn't solve this problem. If anyone has commit access to all micro services, then they can make an even bigger mess, the same way.
In the real world, poorly designed microservices make the ball of mud problem much, much worse, and whatever pain you had in deployments in a monolith are now magnified ten-fold. I have not been fortunate enough to see well-designed microservices, so I suspect the ball of mud is the default. Can this be rectified through discipline? Probably, but I haven't seen it.
Asynchronous? Doesn't save you, when an upstream service changes event definitions and emits events with unexpected structure, and a downstream service starts failing. Can this be rectified through Async OpenAPI and rigorous contract testing? Probably, but I haven't seen this happen in a way that helps.
I have seen large companies survive perfectly well on a monolithic ball of mud, and small companies get lost in a mud pit of microservices.
My point is not that microservices are bad, they're not, they're just a tool, but they are a tool that is a poor fit for most companies, in my opinion. I can't speak to the few giant companies that need them and use them effectively; there is a good reason the tool exists.
I can see that. I am trying to reflect on why I have such negative experience with a monolith and positive with microservices. It's possible that in the monolith setups I had, poor design was easy by default. No linting, no cross-module ownership interlocks, very slow and costly production deploys. Likewise the microservice setups tended to make poor design harder since code is isolated by default both at compile and runtime.
Can you make a monolith with all the good benefits of modularity but without the complications of network RPC etc.? Maybe, but I haven't seen it yet. (Excluding trivial single-team apps; talking about at least 3 teams and 50+ headcount orgs).
> Asynchronous? Doesn't save you, when an upstream service changes event definitions and emits events with unexpected structure, and a downstream service starts failing
Sure, shit happens. Again my experience may be colored, but when costly mistakes happen in monoliths, it tended to take longer to roll back because of how deploys are structured both technically and on an organizational level. I think I would still prefer smaller units in this case.
> heck even a different language
There's nothing about writing software in different languages that necessitates separating functionality with HTTP calls.
I mean it's only computers and the only limit to what we can make them do is our imagination.
In this case though for the sake of argument what options would I have if I, say, needed to let a remote team add some functionality to my, say, Spring backend but they really prefer to write C# and have their own CI/CD system. I'm not sure how I would accomplish this in a monolith.
Also if individual devs can reach out across the codebase and turn private methods public, it is the pull request review procedure that you need to improve, not the architecture.