Part of the problem is the platform itself.
Most mainstream languages today ALL support shared mutable state. Meaning you can hold a pointer or a handle to something, mutate it, and someone else having the same pointer or handle has it changed under their nose.
When you fetch, say, JSON over the network, the party who sent it to you can't change it under your nose either intentionally or accidentally. It's a snapshot of data you own, and you can make decisions on at your own leisure. Sure, the snapshot may get out of date by the time you send your next API request, but this contract is clear and obvious.
While the entangled meshes of mutable code in say C, C++, Java, Swift, Python, JS, etc. etc. often make the mess unavoidable.
Rust goes to some length to stop this problem, but it's a language that's low-level (by modern standards) and technical, and I don't think anyone uses it for enterprise automation exactly.
Another problem are nominal (vs. structural) type systems. Well I don't have to explain, but when you fetch JSON from an API a nominal type won't stop you from reading that data, you care about the structure.
"Shared" implies shared ownership, this is where the confusion comes from. When you ask an API about a user's profile, it's that API's user profile. But the API response itself is entirely yours. It won't change right under your fingertips. The original profile may change, but you know that the profile is not yours already.
Swift has value types which use pass-by-value semantics. Containers use copy-on-write. This is a language where you can quickly build value trees that you know are a guaranteed copy, isolated from whoever sent you this tree.
But most languages have no such infrastructure. Java is adding record types, but they're immutable instead of in-place mutable, which is incredibly inconvenient to work with in nested data structures.
Java's new type offerings are immutable (records, with value and primitive types in the works). Hopefully a push in the right direction.
You say side effects are a problem but you also say the developer cares about his structure in microservices.
When you can’t handle side effects how will you handle microservices?
You could do the same without microservices by just abstracting in libraries which have a defined API.
Microservices will normally make your application more complex. Developers will mess with them too. They can’t change the JSON but some dev will change the API of some service and read the wrong property…
It is also possible to just police it. Supervise it. This is partly anathema now with agile etc. But the iPhone is what it is because there were a few dictators at the top saying "no". This is actually more effective than a physical boundary because:
1) People will hack around the physical boundary anyway.
2) It forces a conversation between the supervisor and the developer every time the supervisor raises an issue. At this point the developer may get a chance to learn something about the reasons for higher level structure. But more importantly the supervisor might learn something about what is wrong with the higher level structure.
But then your project design is now dependent on org hierarchy, and getting the right kind of "people". This isn't guaranteed or easy to do, so I would not consider it a serious situation.
For example from my experience, splitting an Auth/User service for security/compliance, or splitting a Payment service for the same reasons.
Or splitting out a core low-level platform layer that per-product teams can build atop, allowing each to iterate faster, while the Platform layer focuses on clean abstractions.
Nothing in DDD or microservices says you need to sit down and design the service boundaries up-front, indeed most of the advice I’ve seen in the community suggests the opposite.
Rarely have I disagreed with something more strongly :)
If a team lacks the discipline/skill/wherewithal to create good boundaries within a single code base when stakes are at their lowest, they absolutely don't have the skill or discipline to do ahead of time, across N code bases, and with a network in between all of them.
Breaking your system apart too early, and when you know the least about it, and with the rationale that doing so will magically stop the problems that a lack of engineering skill creates, has, at least in the small slice of the world I've encountered, resulted in nightmarish levels of complexity (some of which I'm directly responsible for the reasons stated! (whoops!)).