Introduction
I recently sat down to model the data for a payments + notification-feed
system: customers, projects, a double-entry ledger, PIX payouts, and a
WhatsApp delivery outbox. The kind of domain where one wrong column breaks an
invariant nobody notices until money goes missing.
My usual instinct would be to open draw.io, drag some boxes
around, and connect them with lines. Instead I did the whole thing
in Mermaid — a text-based diagramming language —
while bouncing the design back and forth with an AI assistant (Claude, in my
case), to be clear, I wrote it on my IDE not on the site.
That choice turned out to matter far more than I expected. The diagram stopped
being a picture of the design and became the design itself. Something the
AI could read, critique, and edit with surgical precision, and something I
could commit next to the code as a living spec.
Here's what I learned, in concrete cases.
erDiagram
CUSTOMER ||--o{ PAYMENT : has
PROJECT ||--o{ PAYMENT : receives
PAYMENT ||--o| LEDGER_TRANSACTION : "settles via"
LEDGER_TRANSACTION ||--|{ LEDGER_ENTRY : "has (sum = 0)"
ACCOUNT ||--o{ LEDGER_ENTRY : "posts to"
Case 1: Pin-point corrections on a single column
This is the difference that sold me.
In draw.io, a column lives inside a box, inside a shape, inside a canvas. If
I want an AI to fix one field, I have to describe it in prose, "in the
DELIVERY table, the third attribute, change the type", and then I go click
into the right cell and retype it. The AI can suggest, but it can't touch the
artifact.
With Mermaid, every column is just a line of text:
erDiagram
DELIVERY {
uuid id PK
int feed FK
uuid user_subscription FK
string status "pending | sending | sent | failed"
int attempts "default 0"
string idempotency_key "unique; reserved for z-api dedup"
string last_error "nullable"
timestamp next_attempt_at "nullable; backoff gate"
timestamp sent_at "nullable"
}
When I described my retry-and-backoff requirements, the AI didn't hand-wave.
It proposed exact additions: attempts, next_attempt_at as a "backoff
gate", idempotency_key to make fan-out retries safe. I could see the precise
diff. A correction became "change int id PK to uuid id PK" and the model
edited that single token, leaving the other 200 lines untouched.
One specific kind of correction got dramatically easier:
-
Column semantics. The inline comments (
"signed; +credit / -debit","= SUM(entries)") carry intent. The AI used them to check the design against itself — e.g. confirming that a cachedbalancecolumn is consistent with the ledger entries that derive it.
You simply cannot have that conversation about a PNG.
Case 2: The diagram doubles as a spec — in the repo, in version control
Because the diagram is plain text, it lives in the project:
docs/design/EntityRelation.md
That unlocks everything text gives you for free:
-
It diffs. A pull request that adds a
BANK_ACCOUNTtable or a newstatusenum value shows up as a readable diff in review, right next to the migration that implements it. A draw.io.drawioblob diffs into noise. - It's the source of truth. I treat the Mermaid file as the contract. The Flyway migrations and JPA entities have to match it; when they drift, the diagram is what we reconcile against. The design doc and the schema stop disagreeing.
- It encodes invariants, not just shapes. I dropped comments straight into the diagram for rules the boxes can't express:
%% Invariant: per LEDGER_TRANSACTION, SUM(LEDGER_ENTRY.amount) = 0.
%% Money is only ever moved between accounts, never created/destroyed.
Those two lines are arguably the most important part of the whole system,
and they live with the diagram — readable by humans and by the AI that
later helps implement the ledger.
A drawing on a website is an artifact you export. A Mermaid file is an
artifact you own, version, and review like code.
Case 3: AI reasons better over "diagram-as-code" than over a picture
This is the part I underestimated.
When you hand an AI a picture of a diagram, it has to do vision — OCR the
labels, guess which line connects to which box, infer cardinality from tiny
crow's-foot glyphs. It's lossy and it's slow, and it quietly gets things wrong.
When you hand it Mermaid, you're handing it structured text it already
understands natively. The relationships are unambiguous tokens:
erDiagram
INSTITUTION ||--o{ BANK_ACCOUNT : "pays out to"
INSTITUTION ||--o{ ACCOUNT : "owns (wallet)"
BANK_ACCOUNT ||--o{ LEDGER_TRANSACTION : "receives withdrawals"
||--o{ is "one-to-many" — the model doesn't have to squint at a graphic to
recover that; it's right there in the syntax. So the conversation moved up a
level. Instead of "I think this line means a one-to-many?", we discussed actual
design questions:
- "Should an institution's wallet be its own table, or just an
ACCOUNTwithowner_type = 'wallet'?" (We chose the latter — fewer tables, one balance model.) - "A
WITHDRAWneeds a real-world destination — is the ledgerACCOUNTenough, or do we need a separateBANK_ACCOUNTwith PIX fields?" (Separate table:pix_key,pix_key_type, soft-deleted viastatus = archivedso old withdrawals still resolve.)
The AI could propose a whole new entity by appending well-formed Mermaid, and
I could paste it straight back into the file and render it. The feedback loop
was: discuss → it emits code → I render → I correct one line → repeat. No
export, no re-import, no redrawing.
With a draw.io picture, every one of those turns would have required me to be
the hands — translating the AI's prose suggestion back into clicks and drags.
The AI becomes an advisor you transcribe for, instead of a pair that edits with
you.
Conclusion
draw.io is great when the diagram is the deliverable, a polished picture for
a slide deck. But when the diagram is part of designing a system, and
especially when you're designing it alongside an AI, the calculus flips
completely:
- Corrections are surgical: one column is one line of text the model can edit directly, not a cell you have to find and retype.
- The diagram becomes a spec: it lives in the repo, diffs in review, and carries invariants the schema must uphold.
- The AI reasons natively — Mermaid is structured text, not pixels, so the conversation rises from "what does this line mean?" to "is this the right model?".
The diagram stopped being a drawing of my intent and became a machine- and
human-readable statement of it. One my AI pair could read, challenge, and
co-edit token by token. For me, that's the whole game.
If you're modeling a domain with an AI assistant, try writing the ER diagram in
Mermaid first. Commit it next to your migrations. Let the model edit the text,
not describe a picture. You'll feel the difference on the very first
correction.


