Most products end up with more than one way in. A web app, a mobile app, an admin panel, sometimes an integration nobody remembers approving. Each one is written at a different time, often by a different person, and each one carries its own copy of the rules.
That is where the drift starts. The web app checks that a booking slot is free before writing it. Six months later the mobile app ships the same feature, and the check is subtly different — it forgets that a cancelled booking should free the slot again. Both are "correct" in isolation. The data is now wrong.
The fix is not more discipline. It is moving the rule somewhere no client can skip it.
The test: what happens if a client is wrong?
Not every rule belongs in the database. A rule about which button is disabled belongs in the interface. A rule about how a receipt is worded belongs in the application. The question we ask is narrower:
If a buggy, outdated, or hostile client sent this request, would the result be wrong — not just ugly?
If yes, the rule goes one layer down, where every path has to pass through it. Three cases from our own work make the line concrete.
Correctness that cannot be raced
On the craftsmen marketplace we built, two clients can request the same craftsman for the same slot at the same moment. An application-level "check then insert" loses that race by design: both requests read an empty slot, both write.
So the rule is a partial unique constraint in Postgres. Two bookings cannot both win regardless of which app they came from, because the database refuses the second one. The application still shows a friendly message — but the guarantee does not depend on the application being right.
Money that cannot silently drift
In the agricultural ERP, three invariants sit in the domain layer rather than a review checklist: money is stored as exact decimal and never rounded, double-entry postings must balance on those exact decimals or they are rejected outright, and a posted entry can never be edited — a correction is a new, linked reversing entry.
That last one is the interesting one. "Don't edit posted entries" as a team convention lasts until the first urgent Friday afternoon. As an enforced invariant, it means the audit trail records what was believed and when it changed, permanently.
One more constraint in that system does something quietly remarkable: every posting line must carry an active cost center. That single rule is why profit and loss per farm sector falls out of the ledger as a query instead of being assembled by hand every month. A well-chosen constraint does not just prevent bad data — it makes the good answers fall out for free.
Trust that cannot be bypassed
Row-level security decides who can read what on that same marketplace, and review moderation is enforced in the data layer too, so a hidden review is excluded from the rating it would otherwise skew. Verification is a gate, not a badge: a craftsman passes ID verification and admin approval before they can accept a job at all.
What this costs you
Honestly: some flexibility. Constraints make certain migrations harder, and a rule in the database is less pleasant to change than a line in a service. That is the trade — you are buying the guarantee that it cannot be skipped, and paying for it in change friction.
Which is why the test matters. Push everything down and you get a rigid system nobody can evolve. Push nothing down and you get three codebases quietly disagreeing about what "booked" means. The rules worth pushing down are the small set where being wrong is expensive: money, double bookings, and who is allowed to see what.