StateSet API → StateSet Sync Server (gRPC) Order Dispatch Flow
This document describes a practical architecture for sending orders from the StateSet Rust API (stateset-api) to the StateSet Sync Server (stateset-sync-server) using the Sync Server’s existing gRPC API.
Goal
When an order is created (or updated) instateset-api, reliably dispatch it to stateset-sync-server so the Sync Server can orchestrate downstream integrations (NetSuite, DCL, Cart.com, etc.) via its gRPC surface.
Key Interfaces (as implemented today)
- Sync Server proto:
proto/order_integration.proto(stateset.sync.v1.OrderIntegrationService)- Primary RPC:
SubmitOrder(SubmitOrderRequest) returns (SubmitOrderResponse)
- Primary RPC:
- Sync Server gRPC server:
src/grpc/mod.rs(implementssubmit_order) - Sync Server listen port:
src/server.rs(server.grpc_port, default50051) - Sync Server auth:
x-stateset-api-keymetadata header (src/http/auth.rs) - StateSet API durable trigger: outbox enqueue on order create (
stateset-api/src/services/orders.rs)- Outbox worker:
stateset-api/src/events/outbox.rs - Event loop hook point:
stateset-api/src/events/mod.rs(handle_order_created)
- Outbox worker:
High-Level Flow
- A client creates an order in
stateset-api(HTTP or gRPC). stateset-apicommits the order + items to its DB and enqueues anOrderCreatedoutbox row in the same transaction.- An outbox worker polls for pending events and triggers a Sync dispatch step.
- The dispatcher loads the order + items, builds a
SubmitOrderRequest, and callsstateset.sync.v1.OrderIntegrationService/SubmitOrderon the Sync Server. - The Sync Server validates tenant + API key, then invokes its per-tenant orchestrators to call downstream integrations.
- On success, the outbox row is marked delivered; on retryable failure, it is rescheduled with backoff.
Sequence Diagram
Request Construction (what stateset-api sends)
Minimal viable request (using existing proto)
SubmitOrderRequest requires tenant_id and accepts optional payloads:
stateset_order(CreateOrderPayload) for “StateSet order create” inside the Sync Servernetsuite,dcl,cart(allgoogle.protobuf.Struct) for direct downstream submission
proto/order_integration.proto.
Mapping from stateset-api order model to CreateOrderPayload
stateset-api canonical entities:
- Order:
stateset-api/src/entities/order.rs - Order items:
stateset-api/src/entities/order_item.rs
shopify_order_id(string)order_number(string)created_at(google.protobuf.Timestamp)line_items[]withtitle,quantity,price,sku, etc.
CreateOrderPayload.order_number←orders.order_numberCreateOrderPayload.created_at←orders.created_at(ororder_date, choose one consistently)OrderLineItem.title←order_items.nameOrderLineItem.quantity←order_items.quantity(converti32→f64)OrderLineItem.price←order_items.unit_price(convertDecimal→f64, mind rounding)OrderLineItem.sku←order_items.skuCreateOrderPayload.shopify_order_id:- If you have a true external/channel order id, use it.
- If not, use the
stateset-apiorder UUID string as a stable idempotency key.
stateset_order keys off shopify_order_id (src/orchestrators/order_sync.rs), so this field must be stable for retries.
Auth & Tenancy
- Set gRPC metadata header
x-stateset-api-key(same key used for the Sync Server’s HTTP API). - Provide
tenant_idin every request; Sync Server resolves tenant configuration and validates the key before executing orchestrators (src/grpc/mod.rs).
Delivery Semantics (reliability)
Recommended: outbox-driven gRPC dispatch
To get “at-least-once” delivery to the Sync Server, perform the gRPC call as part of the outbox processing step and only mark the outbox rowdelivered after the RPC is successful (or after a “non-retryable” error policy decision).
This matches the existing outbox pattern in stateset-api (stateset-api/src/events/outbox.rs) but changes the “delivery target” from “in-process channel send” to “cross-service gRPC”.
Retry policy
- Retry on transport/transient errors (
UNAVAILABLE,DEADLINE_EXCEEDED, timeouts). - Do not retry on permanent validation errors without operator action (
INVALID_ARGUMENT,UNAUTHENTICATED,PERMISSION_DENIED). - Treat application-level partial failures carefully:
SubmitOrdercan returnOKbutsuccess=falsewitherrors[]per integration target (src/grpc/mod.rs).- Decide which targets are “required” vs “best-effort” and only consider the outbox event delivered when your required targets succeeded.
Observability
Suggested minimum telemetry on thestateset-api side for each dispatch attempt:
order_id,tenant_id, targetgrpc_addr- attempt count + backoff delay
- gRPC status code on failure
SubmitOrderResponse.successanderrors[].target/messageon application-level failures
Implementation Notes (code placement)
Instateset-api, the simplest integration point is the existing event processing hook:
- Add a Sync dispatcher call in
stateset-api/src/events/mod.rsinsidehandle_order_created. - For true durability, move the gRPC call into the outbox worker path (
stateset-api/src/events/outbox.rs) so outbox “delivered” reflects remote delivery rather than local enqueue.
Open Questions / Future Improvements
- The current Sync Server gRPC proto is integration-oriented and uses
google.protobuf.Structfor downstream payloads. If you want Sync Server to derive NetSuite/DCL/Cart payloads from the canonicalstateset-apiorder model, add a new RPC/message that accepts a first-class “StateSet order” envelope rather than expecting pre-mapped JSON structs. - TLS: the Sync Server gRPC listener is plaintext by default; deploy behind a TLS-terminating proxy or add tonic TLS configuration if needed.