Privacy Is an Architecture Constraint
'Delete my account' is genuinely hard because personal data is scattered across tables — and some of it you're legally required to keep. A runnable lab where a declarative data map drives GDPR rights end to end.
Most engineers meet privacy law as paperwork, then discover it was an architecture decision all along. The moment a user clicks "delete my account", you learn that their personal data is scattered across a dozen tables, that some of those rows are referenced by records you can't delete, and that a few of them you're legally required to keep. None of that is solvable at one endpoint. It's a constraint on how you model and link data — the kind many engineers overlook until it's expensive.
Runnable companion:
privacy-by-design-referenceon GitHub.make up && make seed && make access SUBJECT=1 && make erasure SUBJECT=1 && make verify SUBJECT=1runs the whole flow.
Why "delete my account" is hard
Personal data spreads — across six tables in this small lab, across services, caches, search indexes and backups in a real one. "Erase this person" then hits three problems at once:
- Coverage — you must touch every place their data lives, and not miss one. Each new table is a fresh chance to forget.
- Referential integrity — some rows are referenced by records you must keep, so you can't just delete them.
- Legal holds — financial records, consent evidence and audit trails must be retained, so "erase everything" is actually the wrong answer.
A growing pile of hand-written DELETE statements fails all three over time.
The data map is the architecture
The fix is to stop treating the PII inventory as tribal knowledge and make it an explicit, declarative artifact that the code is driven by. One YAML file declares, per table, which column links a row to a subject, which columns are PII, and what each right does:
| Table | Erasure policy | Why |
|---|---|---|
users | anonymize | keep the row (orders reference it), scrub identifiers |
addresses | delete | contact PII, no reason to retain |
support_tickets | delete | free text may embed PII; also auto-purged after 24 months |
orders | retain | financial record — legal obligation (~10 years) |
consents | retain | proof of lawful basis |
audit_log | retain | record of processing (incl. the erasure itself) |
Every script — access, erasure, verification, retention — iterates this map. Coverage stops being a matter of memory: add a table to the map and the rights machinery covers it. The map is simultaneously a compliance document a DPO can read and the executable policy, so the two can't drift.
Erasure is a cascade, not a DELETE
The right to be forgotten runs as a single transaction that does three different things:
- delete child rows with no retention need (addresses, tickets),
- anonymize the
usersrow — kept, because retained orders reference it, but its identifiers overwritten, - retain the financial, consent and audit records,
and then writes the erasure event itself to the audit log. Deletes run before the parent is anonymized so foreign keys never break.
The verifiable result
A right you can't verify, you can't defend. After erasure, a verification pass scans every mapped PII column and proves the outcome — while showing what was legitimately kept:
[PASS] users 0 PII value(s) not anonymized (expected 0)
[PASS] addresses 0 row(s) remain (expected 0)
[PASS] support_tickets 0 row(s) remain (expected 0)
[KEEP] orders 1 row(s) retained (legal_obligation)
[KEEP] consents 3 row(s) retained (legal_obligation)
[KEEP] audit_log 1 row(s) retained (legal_obligation)
RESULT: PASS — no PII remains; only legally-retained records are kept.
That's a complete, defensible answer to a regulator: the identifiers are gone, the financial record survives de-identified, and the audit log records that the erasure happened.
The one modelling move that makes it work
users is anonymized, not deleted. That single choice resolves the integrity-versus-erasure tension: the orders you must keep retain a valid foreign key to a users row that no longer identifies anyone. The person is erased; the accounting record remains, de-identified. Get this wrong and you get either orphaned foreign keys or illegally deleted financial data.
What I'd say in an interview
- Data-subject rights are a cross-cutting architecture constraint, not a CRUD endpoint — design for them up front.
- Make the PII map an explicit artifact and drive behaviour from it, so coverage is structural, not remembered.
- Erasure is a policy cascade — delete / anonymize / retain — that respects foreign keys and legal holds; anonymizing the subject row is what keeps retained records valid.
- Rights must be verifiable and auditable: prove the erasure, and record that you did it.