Variations
A variation gives some properties of an entity other values in some contexts. It keeps one product, campaign or piece of content while allowing selected properties to differ for a market, channel, language or customer segment.
For example, one product can have its normal campaign headline everywhere, a Swedish headline in Sweden, and a different hero image in the mobile app. Only those values are stored in the variations; every other value continues to come from the entity itself.
Cells
Vox stores each variation as a cell: the values of some properties at a key of contexts, such as Market=se or
Channel=app|Market=se. A key names at most one context of each context type, and a cell only holds the properties it
changes:
[
{ "keys": ["Channel=app|Market=se"], "values": { "campaignHeadline": "Only in the Swedish app" } },
{ "keys": ["Market=se"], "values": { "campaignHeadline": "Only in Sweden" } }
]
A cell can have several keys when the same values apply in several contexts, eg ["Market=se", "Market=no"]. It's
one cell with one version, which applies at each of its keys.
Precedence
When an entity is read in some contexts, Vox applies the cells whose keys match them, from the least to the most
specific. A key matches when each of its contexts is selected, or is a fallback of a selected context. The more
specific key wins: one for Sweden in the app beats one for Sweden, and an exact context beats its fallback. Context
types are ranked by their stacking order, so of Market=se and Channel=app, the one whose type stacks higher wins.
The stacking order is set in code, with VoxOptions.ContextStackingOrder, from the highest to the lowest. Drafts and
releases rank above the application's context types unless it places them. Since the order decides which cells win,
Vox stores it, and a deployment that changes it is migrated: results calculated in drafts and releases are calculated
again, and every durable publication is built in full.
An explicit null is a value. Leaving a property out of a cell means a less specific cell, or the entity itself,
decides it.
Relations
A variation can change relations too, in two ways depending on how the relation is declared:
-
An embedded relation, eg
List<VoxReference<Shelf>> Shelves, is a value like any other: a variation that sets it replaces the whole list in its contexts. A shelf added to the entity itself later doesn't reach that list. -
An index-backed relation,
IVoxRelationReferences<T>, is changed with links and unlinks, eg a product unlinked from a category only in Sweden:{"keys": ["Market=se"],"values": {},"links": { "categories": { "linked": [], "unlinked": [{ "entityType": "Category", "id": "outdoor" }] } }}The changes apply on top of the links of less specific contexts, so a category added to the product itself later is still in Sweden.
Relations read in a selection, both ways, resolve these changes: the categories of the product in Sweden leave out
outdoor, and the products of outdoor in Sweden leave out the product. Filters by relations in queries use the
entity's own links, like other filters.
What can vary
A variation changes presentation or contextual business values; it does not change how an entity is found. Filterable,
sortable, calculated, managed and unique properties cannot vary because filters, sorts, search indexes and uniqueness
rules must see one authoritative stored value. Neither can mirrored data or VoxVarying values, which vary on their
own contexts. Relations can, since filters by relations read the entity's own links.
Calculated values can't be set in a variation, but they follow the variations they read. A product that caches the names of its categories shows the Swedish name of a category in a Swedish read, because Vox calculates the product again where something it read has variations. See Calculations.
This boundary makes variations predictable: first Vox selects an entity through its normal queries and permissions, then it applies the matching cells to the returned representation.
Use VoxVarying when a property intrinsically has a complete set of values by language, market or channel. Use a
variation when there is a useful value of the entity and only some context combinations need another one.
Editorial lifecycle
Each cell has its own version and optimistic-concurrency boundary, so editors of different markets don't conflict. Cells follow the content they're written to:
- Writing a cell to published content publishes it at once, and records a version in the history of the entity.
- Writing a cell to a draft or a content release changes the draft of the entity, and is published with it.
- Deleting a cell in a draft hides the published cell until the draft is published or reverted.
- A draft cell conflicts when the published cell changed after the draft began. The conflict is resolved per cell, by keeping the draft's values or using the published ones.
- Restoring an earlier version of an entity restores its cells as they were at that version.
A content release publishes the entity and its cells atomically with the rest of its items. This makes it possible to launch a market-specific campaign together with the products, prices and content it depends on.
The admin UI edits the cells on the Variations tab of an entity, from the same entity and context metadata used by the API, with a preview of the values in the contexts an editor picks. Another editor can build the same experience without knowledge of a particular Product or Campaign class.
See Languages, markets and channels, Drafts and publishing and Content releases.