Frameworks come and go and the rewrite is annoying but bounded. A wrong schema, three years and two million rows in, is a project.
Where we spend the argument
The entities and their relationships get more discussion in discovery than any other artefact. Specifically: what is a first-class thing versus an attribute, what is genuinely one-to-many, and which relationships are allowed to be optional.
Optionality is the quiet one. Every nullable column is a branch in every piece of code that reads it, forever.
Rules we keep
- Model the business's nouns, not the screens. Screens change every year; invoices do not.
- Store facts, derive views. If a number can be computed, computing it is usually safer than storing a copy that can drift.
- Give every record a real identity and a created timestamp, without exception.
- Money is an integer of minor units plus a currency, never a float.
- Time is stored in UTC with the originating zone recorded when it matters to a human.
Where we relax them
Read performance eventually justifies a denormalised copy. When we add one, it is generated by the system, documented as derived, and has a rebuild path. The failure mode is a denormalised value that a human can edit.
A schema is a set of promises about what can never happen. Make the promises deliberately.
The migration habit
Every schema change ships with a migration, a rollback, and a test that runs it against a copy of production-shaped data. Anything that cannot be rolled back is planned as its own release.
