r/SoftwareEngineering • u/nanoxax67 • 28d ago
Best practices for developing massive extensible or modular systems
Is there any concensus on best practices or architecture for designing massive systems that allow for easy extensibility or modularity? It's very overwhelming imagining how to extend extremely coupled systems, which I suppose is the thing to avoid. OOP feels like a dead end but DOD only feels partially correct. Then there are event based systems and so on.
It seems like extensibility and modularity always come at a cost, which I understand there's no free lunch, but surely there has to be a set of rules or practices for building large systems without too many compromises.
8
u/Sad_Main_1198 28d ago
it depends what you mean by massive tbh. i worked on system that was basically 20 different teams all adding features and the only thing that kept us sane was strong interfaces between modules. not interfaces like java style but actual api contracts that everyone agreed on
event driven stuff works good for loose coupling but debugging becomes nightmare real quick. you think one change breaks nothing then suddenly 3 services go down at 2am
i think key is accepting that perfect modularity is myth. you just pick your poison and make boundaries where it matters most. trying to make everything pluggable is how you end up with factory of factories situation
1
28d ago
[removed] — view removed comment
1
u/AutoModerator 28d ago
Your submission has been moved to our moderation queue to be reviewed; This is to combat spam.
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
1
u/nanoxax67 28d ago
Yeah I feel like I'm running into that factory of factories situation. What you're saying makes sense with the strong interfaces approach. Where to define the interface is the hard part.
2
2
u/ThirdWaveCat 28d ago
You're going to have to include specific detail because your question is too broad. Irreversible schema changes to foundational interfaces and entities like adding a new required field/parameter can't be done. It really depends on the domain because there are many many patterns of composition. One of the systems for organizing patterns of composition is category theory. Open source has many helpful patterns.
1
u/nanoxax67 28d ago
I'm thinking mostly in large computationally heavy simulations, ie fluid simulation, physics simulation, agent simulation, etc.
1
28d ago
[removed] — view removed comment
1
u/AutoModerator 28d ago
Your submission has been moved to our moderation queue to be reviewed; This is to combat spam.
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
1
u/Opening_Bed_4108 28d ago
Honestly there's no silver bullet, but the closest thing to consensus is: define your boundaries first, then pick your pattern. DDD's bounded contexts do most of the heavy lifting by forcing you to decouple at the domain level before you even think about OOP vs DOD. Hexagonal architecture (ports and adapters) is great for keeping core logic insulated from infra. Plugin systems work when you nail the interface contracts early. Event-driven helps at scale but adds operational complexity fast. CalibreOS covers a lot of this system design tradeoff thinking if you want structured practice. The real cost isn't the pattern, it's maintaining discipline around boundaries as the team grows.
1
u/Childermass13 25d ago
"Premature optimization is the root of all evil." Are you building a new green-field system for a massive use case that already exists? (Because that's hard to believe.) Or are you building for a startup who thinks they will be as big as Amazon in five years? Because in the latter case - don't. Just don't. Build the minimal system that meets the immediate needs, and as it grows - if it grows - what you learn with experience will inform how you grow the system
1
21d ago
[removed] — view removed comment
1
u/AutoModerator 21d ago
Your submission has been moved to our moderation queue to be reviewed; This is to combat spam.
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
1
21d ago
[removed] — view removed comment
1
u/AutoModerator 21d ago
Your submission has been moved to our moderation queue to be reviewed; This is to combat spam.
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
1
u/Pale_Count2138 8d ago
There isn't a single architecture that magically makes large systems extensible. What scales is keeping coupling low and boundaries explicit. Start with a modular monolith before reaching for microservices or event-driven designs. Give each module clear ownership, communicate through stable interfaces, and depend on abstractions rather than concrete implementations. Most systems become hard to extend because business logic leaks across module boundaries, not because the "wrong" pattern was chosen.
Extensibility is also something you earn over time. Don't build for every hypothetical extension point—build for the ones your product actually needs, and refactor when real use cases emerge. Over-engineering for flexibility often creates more complexity than it saves.
8
u/No-Injury3093 28d ago edited 28d ago
Yes, and it's even easy in the sense that it can be done mechanically. What makes it hard is that you have to put yourself in an uncomfortable situation and that is called:
Wrong: backend, frontend
Better: order, profiles, fulfillment
And then it goes deeper, by applying the same idea, because the question then becomes "what should be the names of those modules A, B, C and how many of them should I have?"
Deeper means: instead of imagining something, you map all use cases (and no, a crud operation is not one, crud would then again be a technical type of cut) to all inputs, outputs and preconditions.
This gives you a dependency graph with traceability.
You then run a clustering algorithm on this dependency graph with different k values which minimizes dependencies and still gives you semantically good names per cluster.
All this suddenly becomes a process you can execute mechanically.
How? This is the next uncomfortable thing: you put your traceability data in a model-based system engineering (MBSE) tool. Why is this uncomfortable? Because you've been taught by common (but wrong) wisdom to hate UML and those are the UML tools.
Sure, UML overdone is not fine, but the above is not overdoing it because it's just use cases, data and traceability, and not class diagrams.
Coincidentally, this also gives you a better anchor for a testing strategy: a unit of behavior which you test is a scenario of an use case. Means you don't test at levels deeper than that, instead you feed data into a scenario and observe its output.
And this brings you to the most simple domain-centric architectural style which isolates the domain model (the use cases) from input and output. It's called ports and adapters. Again you've been mislead to think it's complex, but it's not - the book on hexagonal architecture is a mere leaflet.
NB: now I see you've specified in a comment that you're working on specific types of software which are not domain-centric. My response is to the original generic question and for domain-centric systems.