4 ms·
A good analogy is lacking though. "Modular Monolith" sounds like a contradiction. It doesn't help the idea. It inherits culture from OOP stuff, that abstractio
by alganet 2y ago
A good analogy is lacking though. "Modular Monolith" sounds like a contradiction. It doesn't help the idea.
It inherits culture from OOP stuff, that abstraction was leaked to repositories, then it was leaked to packages, and it's being roughly patched together into meaningless buzzwords.
It's no surprise no one understands all of this. I see the react folks trying to come up with a chemical analogy (atoms, molecules and so on), and the functional guys borrowed from a pretty solid mathematical frame of mind.
What is the OOP point of view missing here? Maybe it was a doomed analogy from the beginning. Let's not go into biology though, that can't do any good.
Spare parts, connectors, moving parts versus passive mechanisms, subsystems. Hard separation and soft separation. It's all about that when doing component stuff. And it has been figured all out, we just keep messing how we frame it for no reason.
- jerf 2y ago"What is the OOP point of view missing here?" The problem isn't that it is missing something but that it has extraneous parts. Inheritance as your default composition method couples together subtyping and interfaces. You basically can't build a modular monolith out of a large class hierarchy because the very act of being a large class hierarchy means you have more coupling between your classes than a modular monolith permits and already have a just-plain-monolith. From there standard code entropy will only grind it in harder. You really need to eschew inheritance for a good modular monolith and base it around interfaces only. Every major bit of functionality in your system needs a cleanly specified set of services it depends on, where that specification is just an interface and not hard-coded to some specific type, and certainly not some sort of class where subclasses must not only fulfill the interface but conform to the Liskov Substitution Principle, which is much, much harsher than most people realize. Then you have a true module, where if one day you need to yank out a particular service and make it a true microservice, you "just" take the interfaces it needs and either carry along the services locally or run them over the network. I scare-quote "just" because it's not infrequent to need to tweak the interface to permit it to work over the network (more things that can fail), but it's still a relatively mechanical and feasible process versus taking a whole bunch of super-hard-coded specific types, accessed randomly through whatever language scoping mechanism made sense at the time, and trying to convert that to run over the network. This is just a summary, of course, because it's an HN post and I can hardly lay out a complete design philosophy to the n'th degree in a comment. But there definitely is a true distinction between a monolith with all sorts of hard wiring internally and a way you can design a modular monolith that is still a monolith, but the coupling between the modules has been slimmed down to a minimum and controlled through some consistent gate rather than just willy-nilly wired together. Or, to put it another way, it's the difference between the module directly loading another module to directly resolve DNS, directly using the filesystem, directly wiring in a specific type for user authentication, and using a global instance of a logging system instance as initialized by some other module, you know, basically how most people write code all the time, and a module that accepts "a thing to resolve DNS addresses", "a filesystem interface", "a thing that authenticates users", and "a logger" where each of those things are interfaces, minimized down to just what the local module actually needs. One is a coupled nightmare no one want to touch. The other is easily swapped out to work with S3 to store files instead, or inject a different authentication system that ends up using a network service rather than directly hitting the DB, or swap in a hard-coded DNS resolver for testing purposes to avoid dependencies on the real network, etc. It isn't even really anything that surprising. A lot of people will at least claim this is just "good programming". But you don't get the benefits if you don't actually do it.
- alganet 2y ago"True" modules rarely exist in real life. Maybe toys like Lego bricks. Even those have a lot of mismatch sometimes. Sometimes we want to build something that the toymakers never imagined. Pipes and tubes require glue, engines require lubrification oil, bricks require cement. Lots of non-modular things holding stuff together. But we want true modules to exist in programming, right? Why not remove all the glue and oil and messy cement? Make it _truly_ modular, everything fitting together perfectly. I've done plenty on both sides of the argument. Had my fair share of trying to square the circle, only to end up chasing an infinite problem. Increasingly monstrous interface sets, contradictory names, things so abstract I needed a map to understand them. Sometimes using a drop of glue was better than all of it.
- jerf 2y agoRequiring numerous large interfaces is a True Truth about your code. You didn't make anything go away by just giving up. You just forstalled any other future options. The complexity was there regardless. I suspect it is likely you did not do the interface minimization, which I didn't have time to go into. You should not hand "a file system" to your code when it does not need it; if all it needs is to "read a file by file name" then the interface it receives should have only that. If you have the occasional module that takes 25-50 inputs, I just pay the price for that particular module. If every module in your system is taking dozens upon dozens of inputs, you aren't building a "modular monolith", you're just building a monolith with extra random cuts in it, which is the worst of both worlds. You can take an architecture diagram and drop an arbitrarily complicated line across any bit of it. The resulting code will also be arbitrarily complicated, but you can do it. (See also what microservice architectures tend to become anyhow.) But if you keep dropping lines across your architecture and they're all really, really complicated, the problem is your procedure for drawing those lines, not the utility of a separated architecture. This is one of the reasons I'm not a big fan of rigidly following "clean" or "hexagonal" or any other particular architecture, because even as they have good ideas, rigidly following them inevitably involves drawing overcomplicated lines across the architecture. They're useful ways to get started but true design taste requires an inexpressible understanding of where to cut things that none of them can hand to you as a rigid recipe.
- bbor 2y agoOo I love a terminological discussion, well said! I would disagree on your first point though: aren’t most large machines modular monoliths? Say, cars, airplanes, and dams? I absolutely agree that this usage kinda erases the original intent of the word “monolith” in a software context, though. Or at least complicates it greatly… Personally, I’m putting all my money on cognitive, and the terms that go along with it - say, social, agential, discursive, and conversational. Not to forget the deeper cuts from philosophy of mind (the precursor to CS!), such as dialectic (a highly-mutable data structure with holistic modification functions?), architectonic (code-generators built in to the very “top” of a system, breaking it down into a binary tree of generated-code-facilitated subsections?), and striated vs smooth systems (describes the level of obstruction/complication present in each?). Ultimately my takeaway from this article is that absolutes (namely, microservices) rarely work in real world architecture contexts. When looked at in those general terms, I think we have little choice but to start treating software like minds to be “shaped” or “sculpted”, to use Minsky’s preferred terminology. After all, I believe that at the end of the century the use of words and general educated opinion will have altered so much that one will be able to speak of machines thinking without expecting to be contradicted. I believe further that no useful purpose is served by concealing these beliefs. - Alan Turing, 1950’s Computing Machinery and Intelligence
- alganet 2y agoIsn't OOP supposed to be about bringing everyday human things as the paradigm for thinking? Stuff like doors, buttons, locks and keys, levers. There are some good gems in the popular developer culture. "Circuit Breaker" is a de-facto name for an OOP pattern I enjoy. "Dependency Injection" not so much, could be called "Spare Parts Design". Architectonic styles comes from Christopher Alexander stuff. It's a classic, patterns and GoF comes from that. We could use some of his later _Nature of Order_ stuff as well (the "Order" name I don't like that much though, but it's good content nonetheless). I think this is the most cognitive (in what it does, not in what it says) we can get without being too geeky for beginners. Minds to be "educated", maybe? Like we humans do. Small everyday stuff first, then more complicated human things later. Anyway, about the "modular monolith": what about we just call it a "machine"? We already expect most machines to be modular, and the sum of its parts to do more than its individual components, and the idea that the components are designed and maintaned together.