DEV Community

AI Didn't Kill the Need for System Design. It Just Made Bad System Design Easier to Ship.

Dimitris Kyrkos on September 08, 2026

Intro AI Didn't Kill the Need for System Design. It Just Made Bad System Design Easier to Ship. Every "no-code AI" pitch makes the same...
Collapse
 
innokentyb profile image
Kent Bodrov •

One architectural artifact I would add before generation is a table of business invariants and their owners. A schema can be internally consistent and still encode the wrong meaning if nobody states, for example, whether an address belongs to a customer or to an order.

Each invariant should point to the scenarios, contracts, and data migrations it constrains. Then a change can reopen the affected decisions instead of relying on another review of the whole system.

Do you treat those domain invariants as executable checks, decision records, or both?

Collapse
 
cyclopt_dimitrisk profile image
Dimitris Kyrkos •

Both, and honestly the split matters. The executable checks are the safety net (constraints, assertions, contract tests that fail loudly when an invariant gets violated in code), but on their own they only tell you that something broke, not why it was ever a rule in the first place. The decision record is what carries the intent, the tradeoffs considered, and who owns it, which is exactly what you need six months later when someone proposes a schema change that looks harmless in isolation. Executable checks without ADRs turn into cargo-culted assertions nobody dares touch, and ADRs without executable checks turn into documentation that quietly drifts from reality. The pairing is what makes an invariant actually reopenable the way you described, because you can trace from a failing check back to the reasoning, or from a proposed change forward to every check it would invalidate.

Collapse
 
innokentyb profile image
Kent Bodrov •

That split is useful. A decision record preserves why the invariant exists; the check preserves whether the current system still satisfies it.

I would add one more link between them: when the decision changes, the old check should become suspect automatically rather than silently remain green. The change record should name the checks and implementations whose validity must be re-established.

Do you bind checks to a specific decision version, or infer that relationship from the repository when the ADR changes?

Thread Thread
 
cyclopt_dimitrisk profile image
Dimitris Kyrkos •

Right now it's closer to inference than binding, and I've been quietly unhappy about it. The link exists in practice (ADR references the check by name or path, the check has a comment pointing back to the ADR), but nothing enforces that when the ADR is revised the associated checks get flagged as "assumptions changed, re-verify." So in the worst case an ADR moves to v2, the check keeps passing against the v1 assumption because the input space never happened to exercise the difference, and nobody notices until someone reads both side by side. What I want, and haven't fully built, is checks that carry an explicit invariant id and version in metadata, so an ADR bump mechanically marks every check bound to the previous version as stale until someone acknowledges it, either by rebinding to the new version or replacing the check. Basically the same discipline as a migration: the system refuses to consider itself green until the checks and the current decision are on the same version. Are you doing the binding at the check level directly, or through some intermediate registry that both the ADRs and the checks reference?

Thread Thread
 
innokentyb profile image
Kent Bodrov •

I'd bind it at the check level. The ADR owns the current invariant version; each check declares the invariant ID and version it was written against. CI can then report 'stale' separately from pass/fail when the versions diverge. A registry helps discover the fan-out across repos, but I wouldn't rely on it as the only link—the metadata should travel with the executable check. One nuance: I'd bump the invariant version only when the assumption changes, not for an editorial ADR edit. Otherwise people will learn to ignore the stale signal.

Thread Thread
 
cyclopt_dimitrisk profile image
Dimitris Kyrkos •

That versioning discipline is critical because editorial changes shouldn't trigger false alarms that eventually get ignored so only substantive shifts in the invariant should increment the version and mark checks as stale. Binding directly at the check level keeps the dependency explicit and local so the CI pipeline can flag the divergence without waiting for a centralized registry to sync which makes the feedback loop immediate and actionable. It turns the stale signal into a mechanical requirement rather than a manual review task so teams can't accidentally let outdated logic persist just because the documentation was updated without the underlying assumptions changing.

Collapse
 
mudassirworks profile image
Mudassir Khan •

the part about code looking finished before the architecture gets checked is exactly where things get expensive. we call it the confidence gap: AI output has the aesthetic of done code, which makes it hard to push back on in a sprint review.

we’ve started requiring an architecture decision record before any AI assisted feature touches persistence or shared state. forces the question of what the business actually means by its own data before a single prompt gets written. slows the first two days. saves three weeks later.

is the schema judgment call problem worse with AI tooling, or do you see it in traditional codegen too?

Collapse
 
cyclopt_dimitrisk profile image
Dimitris Kyrkos •

That ADR requirement is a smart move because it forces that critical thinking upfront instead of letting it get lost in the code. I think the schema judgment call problem is definitely worse with AI because traditional code generation usually follows explicit templates or strict rules, so you at least know what you're getting, but AI's "aesthetic of done" makes bad assumptions look like intentional design choices, which makes them much harder to spot until they cause real issues.

Collapse
 
aman_social profile image
Aman •

Spot on. The "happy-path trap" with AI is so real—especially around access control (like IDOR) and rate-limit handling. A model will generate clean endpoints in seconds, but it rarely pauses to ask, "What happens if User A swaps an ID in the request parameters?" or "How do we degrade gracefully when a 429 hits?" AI accelerates syntax, but domain judgment and architectural discipline are strictly human work.

Collapse
 
cyclopt_dimitrisk profile image
Dimitris Kyrkos •

Yeah, the IDOR example gets me every time because it's the perfect illustration of how AI-generated code can pass every automated check and still be quietly broken. Linters don't flag it, unit tests pass (because the test was generated from the same flawed assumption as the code), and it only surfaces when someone actually tries to poke at it or, worse, when a real user stumbles into it by accident. The 429 case is similar in that the "fix" isn't code, it's a decision about behavior under stress, and no prompt is going to make that call for you unless you already knew to ask.

Collapse
 
wrobeltomasz profile image
Tomasz •

How can teams ensure robust system design when using AI-generated code

???

Collapse
 
cyclopt_dimitrisk profile image
Dimitris Kyrkos •

The key is to treat AI as a junior developer executing a plan, not as an architect making decisions. You ensure robustness by defining the system design, data models, and security boundaries yourself before prompting for implementation, then using AI to generate the code that fits those constraints. Since AI tends to default to "happy path" scenarios, you must manually enforce rigorous testing for edge cases, failure modes, and concurrency issues that it often overlooks. Finally, keep code reviews focused on architectural alignment and data integrity rather than just syntax, ensuring that every generated component serves a clearly defined human-led design rather than evolving organically into something fragile.