Has adopting microservice architecture changed the way we develop software?
The potentially unsatisfying answer is that microservice architecture has both changed and not changed how we develop software. Starting with the has-not-changed side, the fundamentals of developing software in a professional environment are still the same. This includes best practices such as ensuring close to 100 percent unit/integration test coverage, proper code documentation, consistent code style targeted toward readability, well-named variables and functions, layered abstractions that allow for feature additions and refactoring without extreme pain, and a team that does not include single points of failure.
In my opinion, the biggest thing that microservice architecture has changed is the relative importance of proper abstractions in the above list of fundamentals. Creating the right level of abstraction is one of the hardest things that software engineers do. Too much abstraction and code is hard to understand. Too little abstraction and code is brittle. In large monolithic code bases (whether a service or not—this discussion applies just as well to operating systems and other types of software), abstraction can sometimes be an afterthought because if things get dire, a single heroic changelist can span the entire repository. Microservices force us to think extremely carefully about the right abstractions with regard to feature ownership, data ownership, and communication language/API design. Even if all services are hosted in a monorepo (and I hope they aren’t!), they will never all be deployed at the same time, making backwards compatibility an ongoing concern. There is simply no easy way to fix abstraction mistakes without complex migrations that consume valuable engineering time.
The other major shift that comes with microservice development is the emergence of the network as an unstable substrate that cannot be avoided. Every developer must ultimately deal with networking problems, both in terms of transport and in terms of the vastly more complex tooling required for debugging (i.e., stats, logging, and tracing). Proper abstractions in this area can help greatly—in a sense, this is a specific case of the previous high-level point. For example, the use of Envoy can abstract large portions of the network from application developers, making their jobs substantially easier. Other infrastructure abstractions are possible around storage, service framework tooling, observability, and so on. Successful large microservice deployments inevitably require a big investment in core tooling and application-level abstractions.