Files
web_sport/docs/adr/0016-option-values-are-retained-when-variants-reference-them.md
T
2026-08-13 23:20:22 +07:00

3.0 KiB

ADR-0016: Option values are retained when variants reference them

  • Status: Accepted
  • Date: 2026-08-12

Context

A merchandiser removes the "Red" colourway from a product. What should happen to the three Red variants and to the Red option value itself?

Three things reference them and each has a different claim:

  • Order history — an order line points at a variant id, and reading that order later needs "Red / M" to still mean something.
  • The variant matrix — Red must stop generating combinations.
  • The storefront — a shopper must not see a Red swatch that can never be selected.

The database already takes a position: ProductVariantOptionValue.optionValue is onDelete: Restrict, precisely so that deleting a value cannot silently orphan or cascade into history.

Decision

Variants are archived, never deleted. A removed combination sets status = ARCHIVED. The row, its SKU and its option links all survive, so an order placed last month still resolves.

Option values are deleted only when nothing references them. If any variant — active or archived — still links to a value, the value is retained. It stops appearing in the matrix and stops being offered, but it continues to exist so the archived variants remain readable.

The matrix is built from the operator's submitted option set, not from the database. Retained values would otherwise regenerate the very variants that were just archived.

The storefront filters option values to those offered by at least one active variant. Without this last step, a retained value renders as a permanently disabled swatch with no explanation.

Consequences

Order history stays intact and readable, which is the constraint that drove everything else. Re-adding a removed colourway later reuses the retained value and its translations rather than creating a duplicate.

The cost is that product_option_values accumulates rows that are invisible to shoppers, and "delete" is not always literally a delete — a merchandiser who inspects the database will find values they thought they removed. The schema comment and this ADR are the mitigation; a future admin screen could surface retained values explicitly.

This was found the hard way: the first implementation deleted values before archiving variants and hit the Restrict constraint as a 500. The failure was the schema doing its job.

Alternatives considered

Cascade the delete. Removes the option value and its variant links, which either breaks order lines or cascades into them. Rejected — this is the outcome Restrict exists to prevent.

Soft-delete option values with an isActive column. Functionally similar to retention, but adds a column and a filter to every catalog query for a case that is already handled by "is any active variant offering this?". Rejected as redundant state.

Refuse to remove a value that has variants. Simple and safe, but forces the merchandiser to archive six variants by hand before they can drop a colourway — pushing bookkeeping onto the person the tool exists to help.