ADR-008: Singleton DTOs for Per-Store Configuration¶
Status: Accepted Date: 2025-09
Context¶
Several configuration concerns in the Pricer EVO platform are store-wide: they apply uniformly to all items or ESLs within a store and are changed infrequently by store administrators or platform operators, not by per-entity events. Examples include:
eccparameters— global parameters used in ECC model rendering (key-value pairs substituted into conversion scripts, plus IPF configuration).itemproperties— the set of item property keys available in a store and their display names.itemprocessingparameters— rules governing how Item Registry accepts and applies incoming backoffice item updates (unknown_item_behavior,sic_append,sic_allow_moving_sic,reject_update_on_unlinked).
Each of these could in principle be modelled as a collection of records — one per parameter key, or one per property definition. Two alternatives were considered:
Option A — Collection DTO: Each configuration entry is a separate DTO record keyed by parameter name or property ID. Writers add, update, or delete individual entries; readers subscribe to changes by entry.
Option B — Singleton DTO: The entire store configuration is one DTO record with a fixed, predictable ID. Writers replace the whole record on any configuration change; readers get the complete current configuration in a single read.
Why the collection model was rejected¶
Parameter sets like eccparameters have O(10–100) entries that are always read together by the rendering engine. Reading them as a collection requires the consumer to assemble a complete view from many individual DTO records, handle partial states during bulk updates, and subscribe to a fanout of individual-record change notifications. Because these parameters are always used as a unit, splitting them into individual records adds complexity for no benefit.
For itemprocessingparameters in particular, the parameters form a coherent policy: all four fields together determine how a single incoming item update is processed. There is no meaningful consumer of sic_append that does not also need unknown_item_behavior. Splitting the policy into individual DTOs would require consumers to handle the case where the parameters are partially updated — a transient inconsistency that does not exist with the singleton model.
Decision¶
Configuration DTOs that apply store-wide and are always consumed as a unit are modelled as singletons: one record per store with a fixed ID pattern.
The ID pattern follows the convention established by eccparameters:
Examples:
| DTO | ID example |
|---|---|
eccparameters |
t/10001/s/S001/eccparameters |
itemproperties |
t/10001/s/S001/itemproperties |
itemprocessingparameters |
t/10001/s/S001/itemprocessingparameters |
The singleton record contains all fields for that configuration domain. Writers replace the complete record when any field changes. There is no partial-update mechanism; the writer is responsible for reading the current record, applying the change, and writing the updated record.
A singleton DTO does not exist until it is first written. Consumers must handle the absent-record case as equivalent to "all fields at their default values."
Future DTO groupings¶
As Pricer Server parameters are migrated to DTOflow, the following singleton DTOs are expected to follow the same pattern:
| Future DTO | Domain |
|---|---|
pfiparameters |
PFI drop-folder pipeline configuration |
itemexposureparameters |
Item property filtering for API responses |
eslassignmentparameters |
ESL linking rules (moving PL, auto-unlink, item must exist) |
esloperationsparameters |
ESL partial update, calibration, off-view image settings |
roamingparameters |
Roaming retry intervals, sweep counts, purge thresholds |
Consequences¶
Easier: - A service that needs the complete store configuration issues a single Reader API call and gets the entire record. There is no assembly step. - The record's ID is deterministic and requires no lookup. Given a tenant ID and store ID, any service can derive the configuration DTO ID without querying an index. - CQS subscriptions for configuration changes are simple: one record type, one notification per store on any configuration change. - Default behavior is well-defined: a missing singleton is equivalent to all fields at their proto3 zero values (which are chosen to be safe defaults in each DTO).
Harder:
- The entire record must be written on any field change. If two operators attempt to update different fields of the same store's configuration concurrently, one write will overwrite the other's change (last-writer-wins). This is acceptable for infrequently-changed store configuration but would be unacceptable for per-entity data.
- There is no history of individual field changes within a record. DTOflow's history service records versions of the full record, not field-level diffs.
- Consumers cannot subscribe to changes in a single field. A change to any field in eccparameters notifies all consumers of that DTO, including those that only care about IPF configuration. At the current scale this is not a problem, but it limits fine-grained reactivity.
Trade-offs accepted: - Last-writer-wins on concurrent edits is acceptable for store-wide configuration, which is changed by administrators at human timescales, not by automated per-event processing. - The lack of field-level history is accepted. Audit requirements for configuration changes can be met by comparing consecutive record versions from the DTOflow history service.