PIM - Domain Invariants & Business Rules
Non-negotiable enterprise constraints, SKU uniqueness laws, option collision prevention formulas, completeness publication gates, and BOM acyclicity rules.
Domain Invariants & Business Rules (“The Law”)
In Domain-Driven Design, Domain Invariants represent the non-negotiable enterprise rules and mathematical boundaries that safeguard systemic integrity. As emphasized in Vlad Khononov’s Learning Domain-Driven Design, invariants are not discretionary configuration options—they constitute “The Law” of the bounded context. Under no circumstances may an aggregate commit changes that leave the system in violation of an invariant.
In the PIM domain, invariants prevent catastrophic operational breakdowns across downstream engines, such as duplicate barcode scans at checkout, unfulfilled orders resulting from incomplete specifications, or shop floor material shortages caused by corrupted BOM formulations.
1. Global Identity & Barcode Uniqueness Laws
Stock Keeping Units (SKUs) and optical barcodes (GTIN, EAN, UPC) function as universal identity anchors across warehouses, retail counters, and carrier logistics. They must remain unconditionally unique:
flowchart TD
Assign["Barcode Assignment Request<br/>(SKU, BarcodeString)"] --> CheckDigit{"Passes GS1 Modulo 10<br/>Check Digit Algorithm?"}
CheckDigit -->|No| RejectCheck["Reject: InvalidBarcodeChecksumException"]
CheckDigit -->|Yes| CheckActive{"Assigned to Any Other<br/>Active Variant?"}
CheckActive -->|Yes| RejectConflict["Reject: BarcodeCollisionException"]
CheckActive -->|No| CheckLocked{"Variant Already Published<br/>with Different Barcode?"}
CheckLocked -->|Yes| RejectLocked["Reject: TransactedBarcodeLockedException"]
CheckLocked -->|No| Approve["Commit: Barcode Linked & Event Emitted"]
Identity Invariant Rules
| Invariant Rule | Mathematical / Logical Expression | Business Rationale & Enforcement Mechanism |
|---|---|---|
| Universal SKU Uniqueness | $\forall V_1, V_2 \in \text{Catalog}, ; V_1.\text{sku} = V_2.\text{sku} \implies V_1 = V_2$ | SKU codes must remain unique across all active, deprecated, and archived items. Enforced via aggregate validation and unique database indexes. |
| Optical Barcode Uniqueness | $\forall V_1, V_2 \in \text{ActiveVariants}, ; V_1.\text{barcode} = V_2.\text{barcode} \implies V_1 = V_2$ | An optical barcode may belong to at most one active product variant at any given moment to prevent hardware scanner misidentifications. |
| Barcode GS1 Check-Digit Validity | $\text{GS1CheckDigit}(S) = (10 - (\sum_{i=1}^{n-1} w_i \cdot d_i \pmod{10})) \pmod{10}$ | Optical barcodes (EAN-13, UPC-A, GTIN-14) must strictly satisfy official GS1 Modulo 10 check-digit algorithms prior to assignment. |
| Post-Publication Identifier Immutability | $V.\text{status} \ge \text{Published} \implies \Delta(V.\text{sku}) = \emptyset \land \Delta(V.\text{barcode}) = \emptyset$ | Once an item is published and transacted, its SKU code and barcode are permanently immutable. Adjustments require creating a superseding SKU. |
| No Cross-Product Reassignment | $\text{Barcode}(V) \notin \text{History}(P_{\text{other}})$ | A barcode or SKU previously assigned to one product line cannot be recycled or transferred to an unrelated product family. |
2. Variant Matrix Collision Prevention Laws
Under any parent product, each child variant must represent a distinct physical or functional permutation. Overlapping option combinations are mathematically forbidden:
flowchart TD
Gen["Variant Generation Request<br/>Parent Product P"] --> Coverage{"Every Variant Contains<br/>All Defined Option Axes?"}
Coverage -->|No| RejectCoverage["Reject: IncompleteOptionTupleException"]
Coverage -->|Yes| Collision{"Duplicate Option Tuples<br/>Detected in P?"}
Collision -->|Yes| RejectCollision["Reject: OptionTupleCollisionException"]
Collision -->|No| LockAxis["Lock Parent Option Axes<br/>Commit Child SKUs"]
Matrix Invariant Rules
| Invariant Rule | Logical Expression | Enforcement & Architectural Boundary |
|---|---|---|
| Unique Option Tuples | $\forall V_a, V_b \in P, ; a \neq b \implies \text{Options}(V_a) \neq \text{Options}(V_b)$ | Under parent product $P$, no two variants may share identical option values. Enforced inside the Product aggregate boundary. |
| Complete Axis Coverage | $\forall V \in P, ; | \text{Options}(V) |
| No Orphan Variants | $\forall V, ; \exists ! P \text{ s.t. } V \in P.\text{variants}$ | A variant cannot exist without an active parent product container. Deleting a parent product while child variants exist is strictly blocked. |
| Option Axis Locking | $ | P.\text{variants} |
Combinatorial Permutation Formula
For a configurable product configured with $n$ discrete option types, where each option type $i$ possesses $k_i$ allowable option values, the theoretical maximum Cartesian permutation set is:
$$ |V_{\text{theoretical}}| = \prod_{i=1}^{n} k_i $$
While businesses frequently produce a Sparse Matrix ($|V_{\text{actual}}| \le |V_{\text{theoretical}}|$), the domain guarantees that the actual set contains zero collision tuples:
$$ \forall V_a, V_b \in P, \quad a \neq b \implies \left( \bigcup_{i=1}^n {(\text{Type}i, \text{Val}{a,i})} \right) \neq \left( \bigcup_{i=1}^n {(\text{Type}i, \text{Val}{b,i})} \right) $$
3. Dynamic Attribute Family & Schema Conformance Laws
Dynamic attributes provide merchandising agility, but they must strictly conform to their bound Attribute Family schema to prevent data corruption:
| Invariant Rule | Invariant Description | Architectural Enforcement |
|---|---|---|
| Mandatory Family Binding | Every product must be bound to exactly one active Attribute Family upon instantiation: $P.\text{family} \neq \emptyset$. | Creation action rejects unassigned product shells. |
| Strict Schema Whitelisting | A product may only store attribute values for attributes explicitly declared within its bound Attribute Family: $\text{Keys}(P.\text{attributes}) \subseteq \text{FamilyAttrs}(P.\text{family})$. | Update action validates attribute keys against the family template. |
| Type & Boundary Conformance | Attribute values must strictly validate against their declared data type (Numeric, Text, Boolean, Date, Choice) and boundary ranges (e.g., minimum values, regex masks). | Domain validators enforce regex patterns and numerical bounds. |
| Unit Registry Conformance | Any attribute defined as a physical measurement must specify an approved unit code from the global Unit Registry (e.g., mm, cm, g, kg, ml, l). | Unit codes are validated against the international standard registry. |
4. Channel Completeness & Publication Gates
A catalog item cannot be syndicated to an external sales channel or retail register unless it satisfies 100% of the channel’s mandatory completeness criteria:
flowchart TD
Request["Action: PublishToChannel(Product, Channel)"] --> Gate1{"Channel Completeness<br/>== 100%?"}
Gate1 -->|No| FailScore["Reject: CompletenessGateViolation<br/>Return Missing Attributes List"]
Gate1 -->|Yes| Gate2{"All Channel Locales<br/>Fully Translated?"}
Gate2 -->|No| FailLocale["Reject: MissingLocaleTranslationException"]
Gate2 -->|Yes| Gate3{"Configurable Item Has<br/>At Least 1 Active Variant?"}
Gate3 -->|No| FailVariant["Reject: MissingActiveVariantsException"]
Gate3 -->|Yes| Gate4{"Verified Hero Image<br/>Attached?"}
Gate4 -->|No| FailImage["Reject: MissingHeroAssetException"]
Gate4 -->|Yes| Approve["Commit: Status = Published<br/>Emit: ProductPublished Event"]
Multi-Dimensional Completeness Vector Formula
For a target sales channel $C$ and required locale $L$, the completeness score is computed as:
$$ \text{Completeness}(C, L) = \frac{\sum_{a \in \text{ReqAttrs}(C)} \text{IsComplete}(a, C, L) + \sum_{m \in \text{ReqMedia}(C)} \text{IsComplete}(m)}{\sum |\text{ReqAttrs}(C)| + \sum |\text{ReqMedia}(C)|} \times 100% $$
Publication Invariants
- Zero-Tolerance Gating: If $\text{Completeness}(C, L) < 100%$, publication fails immediately, returning an actionable checklist of missing fields.
- Mandatory Hero Photography: Every consumer-facing sales channel mandates at least one primary image asset tagged with the role
hero_imagethat complies with channel minimum resolution requirements. - Active Variant Mandate: A configurable product (SPU) cannot transition to
Publishedon any commercial channel if it possesses zero active child variants (SKUs).
5. Post-Publication Immutability & Locking Laws
Once a product variant enters commercial circulation and is referenced in historical transactions, its core operational parameters become permanently locked:
| Catalog Parameter | Post-Publication State | Operational Modification Protocol |
|---|---|---|
| SKU Code | Permanently Locked | Cannot be altered in place. Adjustments require creating a superseding SKU and deprecating the original. |
| Optical Barcode (GTIN) | Permanently Locked | Cannot be edited or reassigned. Updating packaging barcodes requires generating a new variant SKU. |
| Base Unit of Measure (UOM) | Permanently Locked | Changing UOM (e.g., from Piece to Box of 10) corrupts historical stock balances. Mandates a new SKU. |
| Variant Option Values | Permanently Locked | A variant configured as Size = Medium cannot be re-typed to Size = Large. |
| Gross Weight & Packaged Dimensions | Strict Governance | Modifications exceeding $\pm 5%$ require warehouse approval to prevent freight rate errors and bin overflows. |
6. Bill of Materials (BOM) Formulation Laws
For assembled merchandise, industrial kits, and subcontracted items, engineering formulation recipes must satisfy strict mathematical graph constraints:
flowchart LR
subgraph Valid_DAG["Valid Acyclic DAG Recipe"]
A["Assembly A"] --> B["Sub-assembly B"]
A --> C["Component C"]
B --> D["Raw Material D"]
end
subgraph Forbidden_Loop["Forbidden Circular Dependency"]
X["Assembly X"] --> Y["Sub-assembly Y"]
Y --> Z["Part Z"]
Z -.->|"FORBIDDEN Recursive Loop"| X
end
BOM Invariant Rules
| Invariant Rule | Mathematical Constraint | Business Rationale & Enforcement |
|---|---|---|
| Strictly Acyclic Graph (DAG) | $P_A \notin \text{Descendants}(P_A)$ | A Bill of Materials cannot contain itself directly or transitively. Circular loops cause infinite recursion during MRP explosion. |
| Positive Consumption Quantities | $\forall c \in \text{BOM}.\text{components}, ; Q_c > 0$ | Every component line must declare a consumption quantity strictly greater than zero. Zero or negative values are rejected. |
| Bounded Scrap Allowances | $\forall c \in \text{BOM}.\text{components}, ; 0.0 \le S_c < 1.0$ | Scrap allowances represent material loss and must be bounded between 0% and 99.9%. Negative or $\ge 100%$ scrap is invalid. |
| Revision Lock on Approval | $\text{BOM}.\text{status} = \text{Approved} \implies \Delta(\text{BOM}) = \emptyset$ | Approved engineering revisions are immutable. Changes require formal creation of a sequential revision (e.g., Rev B). |
7. Taxonomy Classification Laws
The Master Taxonomy governs statutory and financial aggregation across the enterprise:
| Invariant Rule | Invariant Description | Systemic Enforcement |
|---|---|---|
| Master Single-Parent Constraint | Every active product belongs to exactly one leaf node in the Master Taxonomy: $\exists ! c \in \text{MasterLeaves} \text{ s.t. } P \in c$. | Aggregate assignment rejects multi-node master bindings. |
| Leaf-Node Assignment Rule | Products cannot be bound to parent or intermediate classification nodes. Assignment must occur at the terminal leaf level. | Category validation checks node children count ($ |
| Taxonomy Tree Acyclicity | Category hierarchies must remain strictly hierarchical trees without cycles or cross-parent links: $\text{Parent}(N) \ne N$. | Tree update actions validate node paths to prevent graph cycles. |