A flush reports a violated unique constraint. How should the write path turn that into a specific business error?
answer
- which rule, not merely that one broke
- the constraint name is the identifier
- name constraints in the migration
- unmapped name means unexpected, log it
- respond after the unit has ended
basics
~20 sRead the constraint name from the translated failure and map it through a table the application owns, never by parsing the message. Because the unit is condemned, build the response from values captured before the flush, after the rollback.
solid answer
~50 sA translated failure says *a unique constraint was rejected*; it does not say which rule the user broke, and a table usually carries several unique constraints. The identifying detail is the **constraint name**, which travels with the failure - so name constraints deliberately in the migration that creates them and keep an explicit map from constraint name to business meaning next to it. Matching on message text instead is the portability defect this whole layer exists to avoid. Two further points bite in practice: not every unique violation is a user-visible duplicate (a technical dedup key, or a retry landing twice, is not), and not every business duplicate trips a constraint (case-insensitive rules, soft-deleted rows, scoped uniqueness). And since the failed flush condemns the unit, assemble the response from data captured before the flush, or from a fresh unit after the rollback.
go deeper
Know that the failure tells you a constraint was broken, not which business rule, and that the constraint's name is what identifies it. Do not read the error text to find out.
Explain the mapping from constraint name to business meaning, why the schema must name constraints deliberately, and why an unrecognised name should be treated as unexpected.
Show that the response is assembled from values captured before the flush or from a fresh unit afterwards, and distinguish technical guards from rules the user should hear about.
Weigh which uniqueness rules belong in the schema at all, and set the convention for naming, mapping and reviewing them so the failure path stays readable as the schema grows.
## Two different things get called a duplicate | | Constraint violation | Business duplicate | |---|---|---| | What it is | the engine rejected a row against a declared rule | a rule about the domain the user must be told about | | Who defines it | the schema | the product | | Detected by | the write failing | matching the rule, however it is expressed | | Always implies the other? | no - some are technical guards or repeated writes | no - some rules no constraint expresses | The write path's job is to decide, for **this** violation, whether it is the second thing. Treating the two as synonyms produces both of the classic bugs: a technical guard rendered to the user as *that name is taken*, and a genuine business rule that no constraint enforces silently letting duplicates through. ## What the failure actually carries A translated constraint violation typically carries the engine's original failure as its cause, and from that the **name of the constraint** that rejected the row. That name is the only stable identifying detail. The message is prose, the numeric code says only *integrity*, and the column list is not always present. This has a practical consequence for schema work: constraints must be **named on purpose**. A generated name is unstable across environments and meaningless to read, so the mapping either breaks or becomes unreadable. Name the constraint after the rule it enforces, in the migration that creates it, and put the mapping to the business meaning where a reader of that migration will find it. ## Building the mapping 1. **Enumerate the constraints on the table** and decide, for each, whether a violation is a business outcome, a bug, or a benign repeat. 2. **Write the map explicitly** - constraint name to outcome - as data the application owns, not as a chain of string matches on failure text. 3. **Give unmapped names a default**: treat an unrecognised constraint as a bug, log it loudly with the name, and surface a generic failure. A new constraint added later then shows up in the logs instead of being silently reported as somebody's duplicate email. 4. **Cover the non-unique kinds too.** A foreign-key or check violation reaching the write path usually means the code let inconsistent data through; those deserve a loud failure rather than a friendly message. ## The response must be built after the rollback The flush that failed condemned the unit and left the tracked objects describing nothing real. So the friendly response cannot be produced by reading through that unit: - Capture what the message needs **before** the flush - the submitted value, the identifier the user typed. - If a lookup is genuinely required (say, to tell the user which of their records already holds the value), do it **after** the boundary has rolled back, in a fresh unit. - Do not attempt to continue the use case after mapping the violation. The mapping produces an outcome to return, not permission to keep writing. ## Violations that are not the user's problem Some unique constraints exist to keep the system honest rather than to express a rule anyone typed: an idempotency key on a request, a natural key that deduplicates an import, a uniqueness guard behind an at-least-once message delivery. When one of those is violated, the right response is usually to treat the operation as **already done** - the row exists because a previous attempt succeeded - and return the same outcome as that attempt. Rendering it as a validation error tells the user something false. ## Rules a constraint will not catch The reverse gap matters just as much. Uniqueness rules that involve case folding, trimming, an active-versus-archived flag, or a scope such as *unique per tenant* only reach the engine if the schema was declared to express exactly that. If it was not, the write path can never receive a violation for them, and no amount of failure mapping will help. Deciding which of those rules should be pushed into the schema and which stay in application logic is a schema-design question rather than a failure-handling one; the failure-handling rule is simply that you can only map violations that the schema can actually raise. ## What good looks like in review - No branch anywhere reads the wording of a failure message. - Every constraint on a write-heavy table appears in the map, or is deliberately marked as *unexpected*. - The user-facing outcome is produced from captured values, after the unit has ended. - Constraint names in the schema read like the rules they enforce, so the map is checkable by eye.
- The constraint names in an existing schema are auto-generated. What do you do?Rename them in a new migration to something that reads like the rule, then build the map against the new names. Until that lands, keep any temporary matching in one place and treat it as debt with a comment, because generated names differ between environments and rebuilt schemas, so a map written against them fails exactly where it matters.
- A unique violation comes from an idempotency key rather than a business rule. What should the caller see?The same outcome the original attempt produced, not a validation error. The row exists because the work already happened, so the operation is complete. Look up the prior result in a fresh unit after the rollback and return it, which is also what makes the endpoint safe to retry.
- Why prefer this over deciding on the failure's numeric code or state string?Those say only that an integrity rule was broken - they cannot distinguish two unique constraints on the same table, which is the entire question. The constraint name is the finest-grained stable detail available, and it is written by you rather than by the engine.
saying these in an interview costs you the question
- Treating every unique violation as a user-visible duplicate message
- Parsing the failure message text to work out which column conflicted
- Relying on auto-generated constraint names that differ per environment
- Reading through the failed unit to build the friendly error message
- Assuming a business uniqueness rule is enforced when no constraint expresses it
- Continuing the use case after mapping the violation to an outcome