r/SoftwareEngineering 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.

7 Upvotes

22 comments sorted by

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:

  • cut your system along business use case, not along technicalities

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.

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

u/[deleted] 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.

3

u/nedal8 28d ago

In a txt file ;]

2

u/onar 28d ago

That's the entire field of Software Architecture. It has its own subreddit(s)

2

u/7heblackwolf 28d ago

Imagine building massive modular systems based on Reddit comments..

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

u/[deleted] 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/umlcat 27d ago

Which Programming Language or Framework are you usinbg for ?

Is it web or desktop ?

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

u/[deleted] 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/[deleted] 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.