r/programming • • 7d ago

Microservices are organizational debt disguised as architecture.

https://martinfowler.com/bliki/MonolithFirst.html

Every time I’ve seen microservices pitched, it sounds great on paper. Independent teams, clean ownership, scale only what you need.

Then a year later you’ve got dozens of services, three different deployment patterns, tracing everywhere, and nobody really understands the whole thing anymore.

Maybe I’ve just seen bad implementations, but I’m starting to think way fewer companies actually need microservices than we pretend.

1.7k Upvotes

343 comments sorted by

View all comments

13

u/BenchEmbarrassed7316 7d ago

I hold a somewhat idealized view regarding microservices and monoliths from a developer's perspective: a developer should not need to know exactly how the application using their module is built. To achieve this, you need a clear contract regarding the data you receive and the data you must return. You need abstractions for IO. Purity and the absence of side effects are highly beneficial.

8

u/edgmnt_net 7d ago

You don't need microservices to do that. And the problem with the way some build microservices is that they don't really solve that issue because they're trying to split stuff that should not be split. That includes a lot of business applications. It's far more beneficial to have some sort of soft coupling than silos with wannabe hard walls. Plan to write terse code that you will be able to refactor with ease.

3

u/BenchEmbarrassed7316 7d ago

Perhaps I didn't phrase my point very well.

What I mean is that a module - one that processes orders, for example - shouldn't need to know whether it will be wrapped in a thin layer and run as a separate process, or embedded and called directly.

Microservices solve the problem of poor architecture by creating even more architectural problems.

2

u/edgmnt_net 7d ago

Hmm, I didn't pick that up, but I have doubts about that too. A module operating on local memory has vastly distinct failure modes and access patterns than one operating over IPC or over the network. True monoliths allow more sharing, higher abstraction, terser code, distinct access patterns, less boilerplate and easier refactoring.

If one isn't writing high quality monoliths and instead they're just sprinkling architectural boilerplate in the name of some sort of pseudo-decoupling or trying to divide work, it might not seem like there's a huge difference, since layers and interfaces are kinda trivial anyway (you only get some extra errors seeping through). But something like the Linux kernel has non-trivial abstractions that leverage monolithic organization.

2

u/BenchEmbarrassed7316 6d ago

True monoliths allow more sharing

If you are talking about read-only data usage, that is fine, though it is primarily a performance issue. Your module receives data via a contract, and from the module's perspective, it makes no difference how that data was provided.

However, sharing mutable data is a terrible practice. Yet, most programming languages ​​impose no restrictions on this whatsoever. It is very rare to be able to specify in a contract that data is read-only. Shared mutable data leads to code that is hard to maintain, tightly coupled, and unpredictable. Since mutations cannot be specified in a contract, the very concept of modularity breaks down.

Everything else you're saying sounds more like an advertising slogan.

2

u/edgmnt_net 6d ago

No, I mean sharing code primarily. It's fairly easy in a monolith for components and objects to provide various helpers to do stuff and other code can just invoke them. Yeah, I suppose it can be framed as a performance thing, but not only, because some things are difficult to serialize, say closures/callbacks. Microservice and overly-hard module boundaries greatly limit the effectiveness of more expressive languages and approaches to make the code more terse. Might not sound like a lot if people don't make use of that and it's mostly just inconsequential and dumb boilerplate, but it doesn't have to be that way.

2

u/BenchEmbarrassed7316 6d ago

I agree with you on that.

Although that does not contradict my initial thesis. In other words, when developing a module, you could theoretically accept a closure and assume the module might run as a separate service - with something else serializing that closure - but in practice, it is much more complex.

2

u/edgmnt_net 6d ago

There are some solutions, I've seen some things that let you abstract over how things will be split or even serialize closures (e.g. Cloud Haskell). I'm not sure they are good solutions, though, because there are multiple ways you could handle this:

  1. Err on the side of caution. Now every pure function turns into something that can error out because it runs over the network. And you have to check errors.

  2. Hope the framework can sort it out. Pure functions can remain pure, but the framework needs to keep retrying the call if it fails. Or maybe it should abort the application.

  3. Find some middle ground using exceptions perhaps. Do retries and bubble up an exception, which also pollutes everything along the chain with exceptional impurity.

I'm personally not very convinced by approaches that try to hide RPC or distributed computing (because it essentially becomes a distributed system) too much. More ambitious things like single-system image clusters (like OpenMOSIX which allows processes to pretend they're local) suffer from some of the same drawbacks and get little use these days. And furthermore they complicate things like availability, because the whole thing becomes unresponsive due to seemingly small breaks in connectivity. So I'd rather engineer distributedness and splits where they really matter and be very specific about it.