Item Pipeline — Deep Dive¶
End-to-end analysis of how item data flows from client to ESL in the target architecture.
Last validated: 2026-06-25 against GCPplatform-dev-p01, Jira epics, and repository analysis
Architecture Overview¶
The Item Pipeline is the most critical data flow in the Replatforming program. It moves item data (prices, properties, status) from retail systems through the cloud platform and ultimately to ESL labels on store shelves.
---
config:
layout: elk
---
flowchart TB
subgraph input["Item Sources"]
ERP["Retailer ERP"]
PM["Plaza Mobile (BFF)"]
CM["Central-Manager"]
end
subgraph cloud["Cloud Item Pipeline"]
IRA["item-registry-api"]
IRW["item-registry"]
SIV[("storeitemvalues.v1")]
end
subgraph subscribers["Parallel Subscribers"]
SLE["studio-link-evaluator<br/>(subs: storeitemvalues, link.v2, commpack)"]
SR["studio-renderer<br/>(subs: storeitemvalues, studiolink, design, storeesl)"]
end
R3["R3Server (edge)"]
ESL["ESL labels"]
ERP --> IRA
PM --> IRA
CM --> IRA
IRA --> IRW
IRW -->|writes| SIV
SIV -.->|subscribed| SLE
SIV -.->|subscribed| SR
SLE -->|may write studiolink| SR
SR --> ESI[("eslimage.v1")]
ESI -.->|subscribed| R3
R3 --> ESL
classDef svc fill:#AED6F1,stroke:#2E86C1,color:#1a1a1a
classDef dto fill:#A9DFBF,stroke:#1E8449,color:#1a1a1a
class IRA,IRW,SLE,SR,R3 svc
class SIV,ESI dto
1. The Item Concept¶
An item in Pricer's system represents a product sold in a retail store. Each item has:
| Attribute | Description | Source of Truth |
|---|---|---|
| Item ID | Pricer's internal unique identifier | R3Server → Cloud |
| SIC | Store Item Code — the retailer's own identifier | R3Server → Cloud (PLT-2274) |
| Price | Current shelf price | Retailer ERP / Plaza Mobile |
| Properties | Custom attributes per store/tenant (e.g., weight, promo flags) | Retailer ERP |
| Status | Active, discontinued, out-of-stock | Retailer ERP |
| Links | Associations to ESL label designs | Link Pipeline |
SIC (Store Item Code)¶
Customers often identify items by their own SKU system rather than Pricer's internal ID. SIC support allows mapping:
Two modes: - Default (PLT-2277): One SIC scheme for all items in a store - Customized (PLT-2278): Per-customer SIC mapping rules — blocked, depends on item APIs
2. Components — Detailed Breakdown¶
2.1 Entry Points¶
Client-Facing APIs (R3Server — Today)¶
Currently, item CRUD is handled by R3Server's REST API at:
GET /core/v1/items/{itemId} — Get item details
POST /core/v1/items/{itemId} — Create/update item
PATCH /core/v1/items/{itemId} — Partial update (price change)
DELETE /core/v1/items/{itemId} — Remove item
These are called by: - Plaza Mobile BFF — store associates scanning items, checking prices - Central-Manager — multi-store bulk item operations (PATCH/DELETE + CSV import/export) - Plaza Mobile frontend — item search via BFF
Per-API-Path Routing (Ingress — PLT-2101)¶
The migration mechanism. The ingress-nginx is configured to route:
/store.pcm.pricer-plaza.com/api/.../items/* → cloud (item-registry-api)
/store.pcm.pricer-plaza.com/api/.../private/* → R3Server (stays)
Status: 🔵 Selected for Development. Not yet started (Saikiran Katta, currently on vacation). Without this, item traffic cannot be incrementally shifted to the cloud.
Apigee Gateway¶
Used for newer cloud-native integrations (Designer, studio, actions). Also routes item search queries to item-registry-api.
2.2 item-registry-api (Cloud Run — REST/gRPC Endpoint)¶
Service: item-registry-api — the cloud-side item API endpoint.
| Aspect | Detail |
|---|---|
| Protocol | REST + gRPC (dual) |
| Port | Standard Cloud Run (auto-assigned) |
| Status | ✅ Live, partially built |
| Repository | PricerAB/platform-item-registry |
What it does:
- Accepts item PATCH/CREATE/DELETE requests from clients
- Validates incoming item data (PLT-2651 — not yet implemented)
- Writes requests to ItemRequest table in item-registry Spanner database
- Delegates async processing to item-registry worker
- Returns status/result to the calling client
Key story: PLT-2510 — "Implement the item-registry endpoints and business logic" - Written and merged directly to main branch (no PR) - Status: 🟡 Ready for Test, but blocked by PLT-2651 (property validation) - Assignee: Unassigned
Endpoints (target):
PATCH /t/{tenantId}/s/{storeId}/items/{itemId} — Update item
POST /t/{tenantId}/s/{storeId}/items — Create item
DELETE /t/{tenantId}/s/{storeId}/items/{itemId} — Delete item
GET /t/{tenantId}/s/{storeId}/items/{itemId} — Get item
GET /t/{tenantId}/s/{storeId}/items — Search/list items
The t/{tenantId}/s/{storeId} prefix is the tenant+store isolation pattern. Every request is scoped to a specific store within a specific tenant.
What's missing: - Item property validation (PLT-2651) — the API currently accepts any custom properties without checking if they're valid per store - Once validation is implemented, the endpoints are ready for Test
2.3 item-registry (Cloud Run — State Machine Worker)¶
Service: item-registry — the async processor that handles item state transitions.
| Aspect | Detail |
|---|---|
| Role | State machine worker |
| Status | ✅ Live, partially built |
| Deployments | Planned split into 2 deployments (PLT-2512) |
What it does:
- Reads pending ItemRequest records from Spanner
- Processes each request through its state machine:
storeitemvalues DTO to Spanner
- Publishes change event to Pub/Sub: dtoflow-changes-storeitemvalues.v1
- For deletions: marks the record as tombstoned, publishes change event
Key story: PLT-2512 — "Split item registry into 2 deployments" - Separates read-path from write-path for better scaling - Status: 🟡 Ready for Test
2.4 Spanner Storage¶
Database: item-registry¶
| Table | Purpose | Used By |
|---|---|---|
ItemRequest |
Tracks each item request through processing (tenantId, storeId, requestId, receivedTime, processedTime, type, status) | item-registry-api writes, item-registry reads/updates |
This is a request tracking table — it holds the processing state of each item operation, not the item data itself.
Database: dtoflow — relevant tables¶
| Table | Purpose | Key Fields |
|---|---|---|
storeitemvalues |
Current item price/properties per store | dto_type="storeitemvalues", id={tenant_store_item}, DATA (protobuf), checksum |
itemproperties |
Valid property definitions per store | dto_type="itemproperties", id={property_key}, DATA (protobuf) |
itemprocessingparameters |
Item processing configuration | dto_type="itemprocessingparameters", id={config_key}, DATA (protobuf) |
The DATA column is a protobuf-serialized payload. The schema is defined by the DTO type — gRPC clients provide type-safe access.
Data flow:
1. Client sends item data → item-registry-api
2. Validation checks itemproperties table for valid property definitions (PLT-2651)
3. item-registry writes to storeitemvalues table
4. Checksum ensures data integrity
2.5 Pub/Sub — Change Events¶
When an item is written or updated, the item-registry publishes to:
Event payload: Contains the storeitemvalues DTO data and change type (CREATE/UPDATE/DELETE).
Consumers:
- studio-link-evaluator — subscribes to storeitemvalues.v1 via CQS; re-evaluates CEL rules against new item values
- studio-renderer — also subscribes to storeitemvalues.v1; renders with current studiolink + new item values
- ecc-image-render-service — subscribes to storeitemvalues.v1 (via by_item alias on ecclink)
Multiple services subscribe to the same topic and all receive notifications in parallel — there is no sequential dispatch.
Dead letter queue: Failed events go to a DLQ topic for manual inspection.
2.6 Item Property Validation (PLT-2651 — The Gate)¶
This is the single concrete technical task blocking the entire Item Pipeline.
The problem:
item-registry-api accepts:
{ "price": 9.99, "weight": "500g", "promo": "BOGO" }
But there's no check if "weight" or "promo" are valid properties for this store.
What needs to be built:
- When a store is onboarded, its valid item properties are defined in the itemproperties Spanner table
- Before writing to storeitemvalues, the item-registry must query itemproperties and validate that all provided properties exist
- Properties that don't exist should be rejected or flagged
Why it's complex:
- Properties vary per store (a grocery store needs "weight" and "best-before", a clothing store needs "size" and "color")
- Properties vary per tenant (retailer-specific custom fields)
- The validation needs to handle partial updates (PATCH) — a client should be able to update only the price without resending all properties
- The itemproperties table must be populated before validation can work
This is a single story, well-scoped. Written June 1. No assignee, no comments, no sub-tasks.
2.7 SIC Population (PLT-2587)¶
Purpose: Populate SIC (Store Item Code) mappings in the item registry so items can be looked up by the retailer's own identifier.
What it does: - Reads SIC-to-item mappings from R3Server export - Writes them to the item-registry - Enables lookup by SIC alongside item ID
Status: 🟡 Ready for Test. Depends on upstream item pipeline being operational.
2.8 Multistore Support (PLT-2526)¶
Purpose: Handle item operations that span multiple stores — a chain-wide price update or a batch deletion.
What it does:
- Accepts a list of storeIds in a single request
- Fans out the operation to each store's storeitemvalues record
- Uses store DTOs for efficient batch processing
Status: 🟡 Ready for Test. Depends on deployment split (PLT-2512).
Why it's complex: - A chain may have hundreds of stores — a single request can fan out to hundreds of Spanner writes - Partial failure handling: what happens if store 50 of 200 fails? - Transactional integrity across stores is not possible in Spanner (no cross-row transactions at that scale) - The client needs async polling to get results
3. Item Pipeline State Machine¶
Every item operation goes through a state machine:
---
config:
layout: elk
---
stateDiagram-v2
[*] --> Received: Client sends request
Received --> Validating: item-registry picks up
Validating --> Processing: Validation passed (PLT-2651)
Validating --> Failed: Invalid properties
Processing --> WritingSpanner: Write to storeitemvalues
WritingSpanner --> PublishingEvent: Write successful
PublishingEvent --> Completed: Event published to Pub/Sub
WritingSpanner --> Failed: Spanner write error
PublishingEvent --> Failed: Pub/Sub publish error
Completed --> [*]
Failed --> [*]: Error logged, client notified
For deletions:
---
config:
layout: elk
---
stateDiagram-v2
[*] --> Received: Delete request
Received --> Validating
Validating --> Processing
Processing --> MarkingTombstone: Write tombstone to storeitemvalues
MarkingTombstone --> PublishingEvent: Deletion event
PublishingEvent --> Completed
4. Data Flow for Each Operation Type¶
4.1 Item Price Change (PATCH)¶
The most common operation. A store manager changes a price.
1. Plaza Mobile/BFF → Ingress
PATCH /api/v1/stores/{storeId}/items/{itemId}
Body: { "price": 9.99 }
2. Ingress routes to item-registry-api (if PLT-2101 active) or R3Server (today)
3. item-registry-api:
a. Validates tenantId, storeId, itemId exist
b. Writes ItemRequest to Spanner (status=Received)
c. Returns 202 Accepted with requestId
4. item-registry picks up ItemRequest:
a. Validates "price" is a valid property for this store (PLT-2651)
b. Reads current storeitemvalues from Spanner
c. Merges the new price with existing values
d. Writes updated storeitemvalues to Spanner
e. Publishes dtoflow-changes-storeitemvalues.v1
5. Downstream Execution (both paths run in parallel):
a. studio-renderer (subscribes to storeitemvalues.v1): renders with existing studiolink + new item values
b. studio-link-evaluator (also subscribes to storeitemvalues.v1): re-evaluates; if result unchanged, no write
c. If evaluator writes new studiolink → renderer gets 2nd trigger → renders again with new design
d. esl-image-merger → transmission → R3Server → ESL
Estimated latency budget: | Step | Time | Cumulative | |------|------|------------| | Client → API | ~50ms | 50ms | | Validation | ~20ms | 70ms | | Spanner write | ~50ms | 120ms | | Pub/Sub publish + fan-out | ~100ms | 220ms | | Render (with existing studiolink) | ~500ms | 720ms | | Merge + eslimage write | ~100ms | 820ms | | Transmission → R3Server | ~100ms | 920ms | | IR/RF transmit | ~100ms | 1020ms |
Total: ~1-2 seconds from price change to label update. This matches the current R3Server timing.
4.2 Item Creation (POST)¶
1. Client: POST /api/v1/stores/{storeId}/items
Body: { "itemId": "new-item-1", "price": 5.99, "properties": {...} }
2. item-registry-api creates ItemRequest with type=CREATE
3. item-registry:
a. Validates all properties against store's itemproperties table
b. Creates new storeitemvalues record in Spanner
c. Publishes creation event
4.3 Item Deletion (DELETE)¶
1. Client: DELETE /api/v1/stores/{storeId}/items/{itemId}
2. item-registry-api creates ItemRequest with type=DELETE
3. item-registry:
a. Writes tombstone to storeitemvalues (marks as deleted, doesn't remove)
b. Publishes deletion event
4. Downstream Execution (both paths run in parallel):
link-registry (subscribes to storeitemvalues.v1) → finds associated links
studio-link-evaluator → re-evaluates → renderers → esl-image-merger → transmission → ESL clears
Status: Not yet built. Same gate as PATCH (PLT-2651).
5. Complexity Factors¶
5.1 Tenant Isolation¶
Every item record is scoped to a tenant:
Challenge:
- A missing t/{tenantId} check can leak data between retailers
- PLT-1996 (tenant-id check) was recently closed — all DTOflow servers now validate tenant IDs
- But the item-registry must also enforce this in its own logic
5.2 Partial Updates (PATCH Semantics)¶
A PATCH request should only modify the fields that are sent:
Before: { price: 10.00, weight: "500g", promo: null }
PATCH: { price: 8.99 }
After: { price: 8.99, weight: "500g", promo: null }
Challenge: - Must read-then-write (read current storeitemvalues, merge, write back) - Read-modify-write is not atomic in the target architecture - Concurrent PATCH requests could overwrite each other - Solution: Optimistic concurrency using checksum — read checksum, write with condition that checksum hasn't changed
5.3 Bulk Operations (Central-Manager Use Case)¶
Central-Manager sends item updates for multiple stores simultaneously:
PATCH /api/v1/stores
Body: { "storeIds": ["store-1", "store-2", ..., "store-200"],
"items": [{ "itemId": "item-1", "price": 9.99 }, ...] }
Challenge: - 200 stores × 1000 items = 200,000 individual writes - Each write is a separate Spanner operation - No cross-store transaction possible - Client needs async polling with requestId - Failure handling: partial success needs clear reporting
5.4 Property Validation (PLT-2651)¶
The item-registry must know what properties are valid before accepting data.
Challenge:
- The itemproperties table must be populated first — this requires a setup step per store
- What happens if a retailer adds a new property type? The table must be updated
- Properties can be required or optional — different validation rules
- The validation logic must be fast (under 20ms) to avoid latency impact
5.5 SIC Mapping (PLT-2274)¶
Items identified by the retailer's own SKU need bidirectional mapping:
Challenge: - A single retailer SKU may map to different Pricer items in different stores - SIC mapping requires the item-registry to be operational first - Customized mapping (PLT-2278) means per-customer rules — high complexity
5.6 Data Migration from R3Server¶
Existing items are stored in per-store MySQL databases in R3Server. Moving them to Spanner requires:
- Bulk load (PLT-2598) — initial load of all items. Done (closed).
- Realtime sync (PLT-2483) — continuous export of changes. In Code Review.
- Shadow Mode (PLT-2354) — run both systems in parallel. Selected for development.
Challenge: - The bulk load was a one-time migration. Ongoing sync needs CDC (Change Data Capture) from MySQL - MySQL CDC is expensive — the current approach uses application-level export (R3Server publishes changes), not binlog-based CDC - This means R3Server must be modified to export every item change (PLT-2483)
6. Current Item Pipeline Status¶
---
config:
layout: elk
---
flowchart LR
subgraph client["Client"]
A["API Call"]
end
subgraph built["Built (Ready for Test)"]
B["item-registry-api<br/>PLT-2510"]
C["item-registry worker<br/>PLT-2510"]
D["Split deployments<br/>PLT-2512"]
E["Store DTOs<br/>PLT-2526"]
F["SIC Population<br/>PLT-2587"]
end
subgraph done["Done"]
G["GCP Infrastructure<br/>PLT-2511 ✅"]
end
subgraph gate["Gate"]
H["Property Validation<br/>PLT-2651 🔴 NOT STARTED"]
end
A --> B
B --> G
B --> H
H -->|"If validated"| C
C --> D
D --> E
E --> F
What's built and ready: | Story | Component | Status | Assignee | |-------|-----------|--------|----------| | PLT-2510 | item-registry-api endpoints + logic | 🟡 Ready for Test | Unassigned | | PLT-2512 | Split into 2 deployments | 🟡 Ready for Test | Unassigned | | PLT-2526 | Store DTOs for multistore | 🟡 Ready for Test | Unassigned | | PLT-2587 | Populate SICs in registry | 🟡 Ready for Test | Unassigned | | PLT-2511 | GCP resources (infrastructure) | ✅ Done | Daniel Pettersson |
5.4 Property Validation (PLT-2651)¶
| Ticket | Topic | Status | Notes |
|---|---|---|---|
| PLT-2651 | Item property validation | 🔴 Blocked & Unassigned | ROOT BLOCKER: No validation logic exists. Must be built before APIs accept data. |
| PLT-2378 (Epic) | Item Patch APIs - Core | 🔴 Blocked & Unassigned | ADR exists, 4 child stories ready, but PLT-2651 is the gatekeeper. |
5.5 SIC Mapping (PLT-2274)¶
| Ticket | Topic | Status | Notes |
|---|---|---|---|
| PLT-2278 | Customized SIC handling | 🔴 Blocked & Unassigned | SIC mapping needs working item APIs |
| PLT-2274 (Epic) | SIC Support | 🔴 Blocked | Downstream of item pipeline |
7. Interaction with Other Pipelines¶
DTOflow has no central orchestrator. Each service subscribes to the DTO types it needs. When item-registry writes storeitemvalues, CQS fans out the notification to every subscriber in parallel:
---
config:
layout: elk
---
flowchart TB
IR[item-registry] -->|writes| SIV[("storeitemvalues.v1")]
SIV -.->|subscribed| SLE[studio-link-evaluator]
SIV -.->|subscribed| SR[studio-renderer]
SIV -.->|subscribed| ECCR[ecc-image-render-service]
SLE -->|may write| STL[("studiolink.v1")]
STL -.->|subscribed| SR
SR -->|writes| SEI[("studioeslimage.v1")]
ECCR -->|writes| EEI[("ecceslimage.v1")]
SEI -.->|subscribed| EIM[esl-image-merger]
EEI -.->|subscribed| EIM
EIM -->|writes| ESI[("eslimage.v1")]
ESI -.->|subscribed| PS[pricer-server]
PS --> ESL[ESL hardware]
classDef svc fill:#AED6F1,stroke:#2E86C1,color:#1a1a1a
classDef dto fill:#A9DFBF,stroke:#1E8449,color:#1a1a1a
class IR,SLE,SR,ECCR,EIM,PS,ESL svc
class SIV,STL,SEI,EEI,ESI dto
What depends on the Item Pipeline: - Link Pipeline — link-registry stores item-to-ESL associations. Item ID must exist before a link can be created - Rendering Pipeline — renderer needs item data (price, properties) to generate label images - Shadow Mode — requires realtime item export (PLT-2483) to validate the pipeline end-to-end - Consumer APIs — Plaza Mobile and Central-Manager cannot migrate item operations until the pipeline is live
8. Key Risks¶
| Risk | Impact | Mitigation |
|---|---|---|
| PLT-2651 not assigned | Entire Item Pipeline blocked | Clear the validation story — it's well-scoped, single story |
| 4 ready stories unassigned | No one driving them through Test | Assign reviewer for the queue |
| Per-API-path routing (PLT-2101) not started | Cannot shift item traffic incrementally | Needs attention after Saikiran returns from vacation |
| SIC complexity underestimated | Customized mapping may require significant design work | Start with default SIC (PLT-2277 — Ready for Test) |
| Bulk operation failure handling | Partial success in multistore operations is complex | Build async polling from the start |
| Data consistency during migration | R3Server and cloud may diverge | Shadow Mode validation (PLT-2354) catches this |
9. Current Status Summary¶
| Component | Status | What's Left |
|---|---|---|
| item-registry-api endpoints | 🟡 Ready for Test | Implement PLT-2651 (validation), then deploy |
| item-registry worker | 🟡 Ready for Test | Depends on API endpoints |
| Split deployments | 🟡 Ready for Test | Depends on worker |
| Store DTOs for multistore | 🟡 Ready for Test | Depends on split |
| SIC population | 🟡 Ready for Test | Depends on multistore |
| GCP infrastructure | ✅ Done | Nothing |
| Property validation (PLT-2651) | 🔴 Not started | Single story, needs implementation |
| Item Patch APIs (PLT-2378 — Epic) | 🔴 Blocked | Gated by PLT-2651 |
| Per-API-path routing (PLT-2101) | 🔵 Selected | Not started |
| Shadow Mode (PLT-2354) | 🟡 Selected | Needs item export pipeline |