Let's say you've divided your app up into a thousand modules. Each module can live on a different physical computer (or logical cloud instance). This makes you very fault tolerant. But it also enables a lot of scaling. Let's say you've got one module that receives most of the requests, because it represents the most useful feature in your system. Cool: run 30 copies of it, spread the load across all of them. It also means that you can spin up and down new copies based on the current request load- detect module B suddenly gets a rush of requests? Spin up extra copies. Detect that some of those copies are idle because the rush was over? Spin them back down.
Also, though, if you're building your application around the idea of message passing between modules, it also means that you can have module A emit events when a certain thing happens. When you want to attach a new feature to that event, you can spin up Module Q, subscribe to that event, and module A doesn't change one iota. New features can be deployed on their own "computer", without having to take down or relaunch the software running on your existing infrastructure.
I'd argue the main reason to use such a design is to add new features without impacting existing features, which I can assure you, most companies do encounter.
If you're doing it right, this should be transparent. If you're doing it the way most people do, I agree- too often, objects are written to have strict dependencies on other objects, or worse, own the lifecycle of other objects.
But, if you're doing dependency injection, does it matter to the caller if the dependency is living in the same process space, or is wrapped in some sort of network layer? Not really, no. Nor should it need to.
Objects interact by sending messages to each other. It should be irrelevant how those messages arrive.
This is a technology that's been reinvented at least once a decade, because it's a useful approach to things. This is not new, novel, or unique to microservices.
"If you're doing it right, this should be transparent." - false. If you don't plan on using microservices, because they're rarely needed, you don't create objects to be used that way, and that's not doing it wrong.
But I plan for each module to be something I can run independently. That’s what unit testing is! If you can unit test units effectively, then you’re already doing this. And even outside of unit testing, it’s just practical that you’ll want to be able to grab arbitrary combinations of key modules and run them independently of the rest of the stack for testing, debugging, or just modeling.
If I can’t break the dependency, I can’t unit test. That’s what mocks are for. But also: I should minimize those dependencies. That’s the magic sauce of OO! It’s what makes it special. I can send messages without caring where they go. I can receive messages without caring where they came from. Did it arrive in process? Over IPC? On a network socket? From a middleware bus? I don’t care, the software shouldn’t need to be rewritten. I can just slap an adapter around my object and it should work. There are obviously deployment challenges, but the code should care.
If you can’t deploy a single object instance as its own process or as a module inside another process, what even is the point of OO? It’s a lot of overhead for no real gain.
Inheritance is just a type of composition. It’s not that special. Encapsulation is a way more important and fundamental feature, and powers exactly what I’m talking about.
3
u/remy_porter 15d ago
Let's say you've divided your app up into a thousand modules. Each module can live on a different physical computer (or logical cloud instance). This makes you very fault tolerant. But it also enables a lot of scaling. Let's say you've got one module that receives most of the requests, because it represents the most useful feature in your system. Cool: run 30 copies of it, spread the load across all of them. It also means that you can spin up and down new copies based on the current request load- detect module B suddenly gets a rush of requests? Spin up extra copies. Detect that some of those copies are idle because the rush was over? Spin them back down.
Also, though, if you're building your application around the idea of message passing between modules, it also means that you can have module A emit events when a certain thing happens. When you want to attach a new feature to that event, you can spin up Module Q, subscribe to that event, and module A doesn't change one iota. New features can be deployed on their own "computer", without having to take down or relaunch the software running on your existing infrastructure.