Modular Software Architecture for CAD/CAM and Automation
Modular architecture for engineering software: cut modules by rate of change, not by technology. Module groups, data contracts, decision records and tests.
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.
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.
- 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
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.
The boundary between modules is a data contract, not a function signature. Keep the contract in an explicit type and version that type:
Let's make the contract concrete. Say the rule engine produces a panel. All the CAD layer needs to know is a handful of fields: a schema version, width, height, profile type and a list of openings. Nothing else. How those values were computed inside the rule engine is none of the CAD layer's business.
Put that schema version field in from day one, even if it stays at 1. The reason: adding a field is not a breaking change, but removing one — or changing its meaning — is. When a breaking change arrives you bump the version and write a small converter for the old one. That is far cheaper than having to update every consumer on the same day.
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.
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.
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.
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.
What is modular software architecture? Splitting a system into independent parts connected only by explicit contracts. In engineering software the split follows rate of change, not technology: things that change together for the same reason live in the same module.
Where should a module boundary go? At the reason for change. A geometry core rarely changes, a rule engine changes as production standards move, CAD integration changes as versions rise. Keeping code that changes for different reasons in one module is the most common architectural mistake.
How should a contract between modules be defined? Not with a function signature but with an explicit data type. Give that type a schema-version field and keep it serialisable (JSON/XML); an intermediate format readable on disk is the strongest debugging tool you have.
Why are architecture decision records needed? The most expensive knowledge loss in engineering software is forgetting why a decision was made. Context, decision, consequence and rejected alternatives on a single page is enough; the fourth item stops the same alternative from being proposed again a year later.
What should be tested in which module? Pure unit tests in the geometry core, scenario tests frozen from real production cases in the rule engine, a small number of smoke tests against a real session in CAD integration, and snapshot tests in the output generator.
Why is AI-assisted development more effective in a modular architecture? A module with a clear boundary fits in the model's context window. Two rules apply though: boundary decisions stay with a human, and generated code is not used in a module that has no contract tests.