The situation
Products have variable attributes, nested option groups, bundles and customer-specific fields, and the catalogue never stops changing. Suppliers send spreadsheets in their own layouts. A relational schema creates constant migrations; a loose document column gives up validation and governance. Meanwhile the storefront, the search index and the reporting warehouse each integrate against something slightly different.
These are illustrative scenarios showing how the products are used, not customer case studies.
Build-versus-buy lens
What this helps you decide
Use the example to find where product configuration ends and custom code begins — and whether the boundary leaves your team with less infrastructure to own.
- Model fit
- Can the record shape evolve without coordinating a migration, API and UI release?
- Workflow fit
- Can approvals, exceptions and deadlines move out of application code without losing control?
- Integration fit
- Can your existing APIs become reusable steps with retries, throttling and visible run history?
- Control fit
- Can your team reconstruct model changes, workflow runs and human decisions later?
What gets modeled
The structure as it would actually be defined — sections that repeat are marked.
- Productrecord type
- Identitysection
Code, name, supplier reference
- Categorychoice
Options read live from Categories
- Attributesrepeating section
Varies by category — the reason a fixed column set never held
- Nametext
- Valuetext
- Unitchoice
- Option groupsrepeating section
- Group nametext
- Choicesrepeating section
Nested inside the group
- Categoriesrecord type
- Suppliersrecord type
How it gets built
In the order somebody would actually do it. Each step names the capability it rests on, so it can be checked against the product pages.
Model the variability instead of fighting it
Attributes and option groups are repeating sections, and choices nest inside their group. The structure of a real product survives contact with the system rather than being flattened into columns nobody can name in advance.
What it uses: Sections that nest and repeat to any depth
Send suppliers the shape you expect
Every published version can be imported and exported as CSV or JSON, and a sample file shows the expected shape. The supplier gets the format from the model rather than from a document somebody wrote once.
What it uses: Import and export per published version, with a downloadable sample
Let the model reject the bad rows
Required, unique, typed and validated are properties of the field, so they apply on import exactly as they apply to the API. The import cannot introduce something the product would otherwise refuse.
What it uses: Per-field rules enforced on every path in
Catch the same product arriving twice
What counts as a duplicate is defined on the schema, with a similarity threshold for the likely ones — the same item under a slightly different spelling. The rules are versioned with the schema rather than living in an import script.
What it uses: Duplicate detection with a similarity threshold, versioned with the schema
Make it findable
Full-text search across the record type, weighted so that a name matters more than a description, with language-aware stemming. Where a contains-match is enough, that is available too.
What it uses: Full-text search with per-field weighting
Give every consumer the same contract
The catalogue is published with its own REST URL space and an API description generated from the model, plus a schema description per version. The storefront, the search indexer and the warehouse read the same thing rather than three interpretations of it.
What it uses: Its own API on publish, described by a generated API description
Open the public part, and only the public part
Where the catalogue is meant to be public, anonymous read is opted into for that schema alone — reads only, with anonymous writes refused outright, and available only for schemas that publish on save. Everything else stays behind keys scoped to particular record types.
What it uses: Opt-in anonymous read per schema, and API keys scoped per record type
Push the changes rather than being polled
Downstream systems subscribe to product events and receive them signed, with retries, a delivery log and dead-lettering that can be redriven in bulk. A price change reaches the storefront because it was sent, not because something asked.
What it uses: Signed event delivery with retries, dead-lettering and bulk redrive
Numbers used above, and where they come from
Every figure on this site is counted from the product itself. Nothing here is a performance or customer claim, because there is no measurement to cite for one.
- field types
- 9field typesCounted from egav/backend/app/api/v1/egav/schemas/entity_type_attribute.py — DataType
- places a choice list can come from
- 5places a choice list can come fromCounted from egav/backend/app/api/v1/egav/schemas/entity_type_attribute.py — OptionSourceType (file is reserved and not counted)
- destinations for an event
- 4destinations for an eventCounted from egav/backend/app/api/v1/egav/lib/webhook/transports — http, sqs, sns, kafka
What changes
Onboarding a supplier stops being a script per supplier, the catalogue changes shape without a migration, and every downstream system reads one contract that is generated from the model rather than described in a document.
Written for Teams with data that keeps changing shape.