Modular Approaches in Software Architecture
Practical architecture notes for a sustainable codebase in large-scale industrial projects — module boundaries, data contracts, decision records, and test discipline.
In This Article
In engineering software projects, a modular architecture raises both development speed and field reliability by making domain boundaries explicit. In CAD/CAM and automation products especially, the "one big monolith" approach looks fast in the short term but drives up maintenance cost over the medium term.
Domain to software boundary
In fields such as prefab manufacturing, roll-form lines, or SolidWorks automation, software modules should be split along manufacturing reality: geometry processing, rule validation, output generation, licensing, and the user interface must be able to evolve independently of each other.
Domain knowledge sets the software boundary; AI tools are used as accelerators inside those boundaries.
Suggested module groups
- Core: Geometry, parameter model, rule engine
- Integration: CAD API, file I/O, ERP/MES bridges
- Output: Drawings, DXF/DWG, production data
- Platform: Licensing, logging, configuration, UI
Boundaries: where to cut
Cut modules by rate of change, not by technology. Things that change for the same reason at the same time belong together; things that change for different reasons come apart. On the CAD/CAM side, that produces a table like this:
| Module | When it changes | What it may depend on |
|---|---|---|
| Geometry core | Rarely — the maths does not move | Nothing |
| Rule engine | Often — as production standards shift | Only the geometry core |
| CAD integration | As the CAD version moves | The rule engine's output |
| Output generator | On customer or line demand | The rule engine |
| Platform (licensing, logging) | Independently | No business module |
The one-way rule here matters: no business module may call the platform module, and the platform must not know the business modules. The moment a licence check leaks into geometry code, testing that code requires a licence server — and the tests never get written.
Data contracts and versioning
The boundary between modules is a data contract, not a function signature. Keep the contract in an explicit type and version that type:
// Output of the rule engine — the CAD layer knows only this.
public sealed record PanelSpec(
int SchemaVersion, // incremented on a breaking change
double WidthMm,
double HeightMm,
ProfileType Profile,
IReadOnlyList<Opening> Openings);Put the SchemaVersion field in from the start, even if it sits at 1 for the first release. Adding a field is not breaking; removing a field or changing its meaning is. On a breaking change, bumping the version and writing a converter for the old one is cheaper than having to update every consumer on the same day.
Keep the contract writable to disk (JSON/XML). A readable intermediate format is the most powerful debugging tool you have: a customer's "this job doesn't work" message reduces to a single file.
Decision records: don't bury the why
The most expensive knowledge loss in engineering software is forgetting the reasoning behind a decision. A team that cannot answer "why is this profile calculation like this?" six months later will either leave the code untouched or fix it wrongly.
No heavy process is needed. Short notes under docs/decisions/ in the repository, one page per decision, are enough:
- Context — which constraint forced this decision (line tolerance, CAD API limit, customer standard)
- Decision — what was decided
- Consequence — what it makes easier, what it makes harder
- Rejected alternatives — and why they were rejected
The fourth item is the most valuable; it stops the same alternative being proposed again a year later.
Test discipline: what gets tested where
- Geometry core — pure unit tests. With no dependencies, these are the cheapest and fastest tests; coverage should be highest here.
- Rule engine — scenario tests. Freeze real production cases as input/expected-output pairs; when a standard changes you see immediately which cases moved.
- CAD integration — a small number of smoke tests against a real CAD session. Expensive, hence few — but never zero.
- Output generator — snapshot tests. The produced DXF need not be byte-identical, but layer names and dimensions must stay fixed.
Module ownership in AI-assisted development
AI tools work markedly better in a modular architecture, for a simple reason: a module with a clear boundary fits in the model's context window. In return I set two rules:
- Boundaries stay human. I decide what a module will and will not do, the shape of the contract, and the direction of dependency. If those decisions are wrong, fast-generated code multiplies the mistake quickly.
- No AI in a module without a contract test. If there is no mechanism to verify the generated code, the speed you gain comes straight back as risk.
In practice I get the best results in modules whose inputs and outputs are pure, such as the geometry core; the worst in layers that talk to the CAD API and carry state.
Measure when moving to production
In a pilot → limited users → full roll-out flow, record the same three numbers at every stage: preparation time, error rate, and output consistency. Numerical claims should be supported only by that record — "it got much faster" is not an engineering claim.