ADR-006: ESL Status as a Separate DTO (storeeslstatus)¶
Status: Accepted Date: 2026-05
Context¶
Each physical ESL goes through a defined lifecycle: it is onboarded when first paired, transitions through operational states (OK, updating, roaming) as communication conditions change, and is eventually offboarded or declared lost. PricerServer drives this lifecycle and needs to communicate the current state back into DTOflow so that other services — specifically link-service and storeesl-service — can react to lifecycle transitions.
Two design approaches were considered:
Option A — Inline in storeesl: Add lifecycle state fields directly to the storeesl DTO. PricerServer would write a storeesl update whenever an ESL's state changes.
Option B — Separate child DTO (storeeslstatus): Introduce a new DTO at the path t/{tenantid}/s/{storeid}/esls/{barcode}/status, owned exclusively by PricerServer. The storeesl DTO remains unchanged and is owned by storeesl-service.
The state machine for storeeslstatus has 10 states:
| State | Meaning |
|---|---|
ONBOARDING |
Pairing in progress; device reachable |
ONBOARDING_PENDING |
Pairing timed out; device unreachable |
OK |
Fully operational; image up to date |
UPDATING |
Image or flash command in progress |
ROAMING |
Device unreachable; PS retrying |
LOST |
Unreachable beyond auto-reset threshold (terminal) |
OFFBOARDING |
Offboard command queued; device reachable |
OFFBOARDING_PENDING |
Offboard command queued; device unreachable |
OFFBOARDED |
Successfully offboarded (terminal) |
FAILED |
Configuration error or other non-transient failure |
State transitions are frequent in an operational store. Onboarding and roaming events generate a continuous stream of updates across thousands of ESLs. storeesl records, by contrast, change infrequently — they are created when an ESL is first linked and deleted only when the ESL is permanently removed from the store.
Additionally, the ownership model for the two DTOs differs: storeesl is owned by storeesl-service, but lifecycle state is generated by PricerServer. Mixing them in one DTO would require either dual-ownership (which violates ADR-001) or routing all lifecycle state through storeesl-service (which would make storeesl-service a proxy for PricerServer state, adding unnecessary coupling).
Decision¶
ESL lifecycle state is stored in a separate storeeslstatus DTO at the path t/{tenantid}/s/{storeid}/esls/{barcode}/status. The storeesl DTO is not modified to include lifecycle state.
Ownership:
- storeeslstatus is written exclusively by PricerServer.
- storeesl is written exclusively by storeesl-service.
Consumers of storeeslstatus:
- link-service watches for LOST and OFFBOARDED states to detect auto-unlink conditions.
- storeesl-service watches for LOST and OFFBOARDED states to decide when it is safe to delete the storeesl record.
Deletion flow: PricerServer writes LOST or OFFBOARDED → link-service deletes storeesl → the storeesl deletion is the signal for PricerServer to delete the storeeslstatus DTO.
The storeeslstatus DTO also carries operational detail beyond state: battery level and history, timestamps for last display update, last OK state, and state entry time, plus device-level hardware properties (firmware version, hardware revision, encryption state) and IR-specific status (home transceiver, roaming level, sweep count).
Consequences¶
Easier:
- High-frequency state changes (roaming, updating) do not cause write contention on storeesl. Services watching storeesl for ESL identity changes are not flooded with lifecycle noise.
- Ownership is clear and aligned with ADR-001: each service writes exactly one DTO type in this domain.
- storeesl-service does not need to understand PricerServer lifecycle semantics. It only needs to watch for terminal states in storeeslstatus to decide when to delete storeesl.
- The storeeslstatus ID path (…/esls/{barcode}/status) is a natural child of the storeesl path (…/esls/{barcode}), making the relationship explicit in the ID hierarchy.
Harder:
- Consumers that need both the ESL identity and its current lifecycle state must subscribe to two DTOs.
- The deletion coordination loop (PricerServer writes terminal state → link-service deletes storeesl → PricerServer deletes storeeslstatus) introduces a multi-step async sequence. If any step fails, cleanup is incomplete.
Trade-offs accepted:
- The deletion coordination sequence adds latency to final ESL cleanup. This is acceptable because both LOST and OFFBOARDED are terminal states — there is no correctness requirement to clean up within a bounded time window, only an operational hygiene requirement to eventually clean up.