Skip to content

Integration Architecture Pattern: Temporal-Based Durable Transactions

Date: March 6, 2026
Status: ESTABLISHED PATTERN
Safety Level: CRITICAL


All third-party ERP integrations should follow the Temporal-based durable transaction pattern:

  • Read Operations (GET) β†’ Temporal activities (cacheable reference data) + Dagster batch jobs
  • Write Operations (POST/PATCH/DELETE) β†’ Temporal workflows with retry logic, audit trails, and rollback capability
  • Rationale: Ensures reliability, traceability, and atomicity across network boundaries

This document establishes the pattern once and prevents reinventing the wheel for P21, NetSuite, SAP, etc.


β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ WEBAPP USER ACTION β”‚
β”‚ (e.g., "Submit Order to P21") β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ TEMPORAL WORKFLOW (Durable Transaction) β”‚
β”‚ β”‚
β”‚ 1. Fetch live data from ERP API (GET) β”‚
β”‚ 2. Validate against local rules β”‚
β”‚ 3. Calculate adjustments (pricing, discounts, etc) β”‚
β”‚ 4. Show user confirmation β”‚
β”‚ 5. On approval: β”‚
β”‚ β”œβ”€ Begin transaction β”‚
β”‚ β”œβ”€ Send POST/PATCH to ERP β”‚
β”‚ β”œβ”€ Retry on failure (exponential backoff) β”‚
β”‚ β”œβ”€ Rollback if failed β”‚
β”‚ └─ Log audit trail β”‚
β”‚ 6. Return result (success/failure) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β–Ό β–Ό β–Ό
SUCCESS FAILURE AUDIT LOG
(return) (retry) (store)

AspectTemporal Advantage
DurabilitySurvives network failures, worker crashes
Retry LogicExponential backoff with configurable limits
Audit TrailEvery step logged for compliance
RollbackCan reverse failed writes (compensating transactions)
Timeout HandlingWon’t hang indefinitely on bad connections
OrderingGuarantees steps execute in order
IdempotencySafe to replay without side effects

Status: βœ… READY FOR IMPLEMENTATION

WEBAPP
└─ User submits order
β”‚
β–Ό
TEMPORAL WORKFLOW: submit_order_to_wherefour
β”‚
β”œβ”€ Activity 1: fetch_order_from_api(order_id)
β”‚ └─ GET /orders/{id}
β”‚ └─ Returns: order_items[], custom_line_items[], pricing
β”‚
β”œβ”€ Activity 2: get_pricing_tiers_from_cache()
β”‚ └─ Read Redis (24h TTL, synced by Dagster)
β”‚
β”œβ”€ Activity 3: validate_order(order, tiers)
β”‚ └─ Pure Python: validate products + custom items
β”‚
β”œβ”€ Activity 4: calculate_discount(order, validation)
β”‚ └─ Apply tier discounts + allowances
β”‚ └─ Return discount chip data
β”‚
β”œβ”€ (User confirms in UI)
β”‚
β”œβ”€ Activity 5: submit_to_wherefour(order)
β”‚ └─ ⚠️ ONLY on Sandbox account (when available)
β”‚ └─ BLOCKED for production until sandbox exists
β”‚
└─ Activity 6: audit_log(workflow_id, status)
└─ Store in PostgreSQL audit_trail table
DataOperationSourceWhere
OrdersGETAPITemporal (on-submit)
Order ItemsGETAPITemporal (on-submit)
Custom Line ItemsGETAPITemporal (on-submit)
Pricing TiersGETScraperRedis (Dagster daily)
CustomersGETAPIRedis (Dagster daily)
InventoryGETAPIPostgreSQL (Dagster daily)

⚠️ CRITICAL SAFETY BOUNDARY: NO WRITES TO PRODUCTION

Section titled β€œβš οΈ CRITICAL SAFETY BOUNDARY: NO WRITES TO PRODUCTION”
# ALLOWED (GET only)
βœ… fetch_order_from_api(order_id)
βœ… get_customers_list()
βœ… get_inventory_catalog()
# BLOCKED (POST/PATCH/DELETE)
❌ submit_to_wherefour() # ← Wait for sandbox account
❌ update_order_status() # ← Wait for sandbox account
❌ create_invoice() # ← Wait for sandbox account

RULE: If operation requires POST, PATCH, or DELETE, it MUST target sandbox account first.
STATUS: Sandbox account NOT YET AVAILABLE.
IMPLICATION: CK POC cannot test write operations until sandbox confirmed.


Status: PLANNED (Q2/Q3 2026)
Scope: Refactor existing P21 integration to use Temporal pattern

  • Identify all P21 API calls in codebase
  • Classify: GET (read) vs POST (write)
  • Document current error handling
  • Identify silent failure points
Temporal Workflow: submit_order_to_p21
β”œβ”€ Activity: fetch_p21_customer(customer_id)
β”‚ └─ GET /customers/{id} from P21 API
β”‚
β”œβ”€ Activity: validate_p21_sku_mapping(order_items)
β”‚ └─ Check SKU translations
β”‚
β”œβ”€ Activity: submit_p21_purchase_order(order_data)
β”‚ └─ POST /purchase-orders to P21 (with retry)
β”‚
β”œβ”€ Activity: confirm_p21_receipt(po_id)
β”‚ └─ PATCH /purchase-orders/{id}/confirm
β”‚
└─ Activity: log_audit_trail(workflow_id, p21_po_id)
└─ Store in PostgreSQL
  1. Phase 1: Extract P21 API client library (if doesn’t exist)
  2. Phase 2: Create Temporal activities for core operations
  3. Phase 3: Create Temporal workflows (submit order, confirm, cancel, etc)
  4. Phase 4: Integrate with webapp (replace direct P21 calls)
  5. Phase 5: Add retry logic + exponential backoff
  6. Phase 6: Add audit logging + dashboard

Apply same pattern to:

Temporal Workflows:
β”œβ”€ submit_order_to_netsuite
β”œβ”€ sync_inventory_from_netsuite
└─ pull_customer_data
Read operations (Dagster):
β”œβ”€ Fetch customers daily
β”œβ”€ Fetch tax codes daily
└─ Fetch item catalog daily
Temporal Workflows:
β”œβ”€ submit_sto_to_sap
β”œβ”€ confirm_receipt
└─ sync_shipment_status
Read operations (Dagster):
β”œβ”€ Fetch material master data
β”œβ”€ Fetch cost centers
└─ Fetch plant data
Temporal Workflows:
β”œβ”€ create_invoice_in_qb
β”œβ”€ record_payment
└─ sync_estimates
Read operations (Dagster):
β”œβ”€ Fetch customers
β”œβ”€ Fetch items
└─ Fetch payment methods

Dagster jobs handle read-only, batch operations ONLY:

@job
def wherefour_sync_daily():
"""Sync reference data (NOT transactional)."""
# Fetch reference data
tiers = scrape_pricing_tiers() # Playwright scraper
customers = fetch_customers_from_api() # GET /customers
inventory = fetch_inventory_from_api() # GET /inventory
vendors = fetch_vendors_from_api() # GET /vendors
# Cache in Redis + PostgreSQL
cache_pricing_tiers(tiers) # Redis 24h TTL
cache_customers(customers) # Redis 24h TTL
store_inventory(inventory) # PostgreSQL 7d retention
store_vendors(vendors) # PostgreSQL 7d retention

NEVER use Dagster for writes (POST/PATCH/DELETE). Use Temporal instead.


βœ… OK: GET /customers, GET /inventory, GET /orders
❌ NO: POST /invoices, PATCH /status, DELETE /orders
βœ… OK: Temporal activity submits POST to sandbox
❌ NO: Direct httpx.post() in main code
❌ NO: Direct requests.post() in webapp
Before testing ANY POST/PATCH/DELETE:
1. Request sandbox account from vendor
2. Verify credentials work
3. Run test against sandbox
4. Document test results
5. ONLY then: enable production writes
Every POST/PATCH/DELETE must:
β”œβ”€ Log to PostgreSQL audit_trail
β”œβ”€ Include workflow_id + timestamp
β”œβ”€ Include full request payload
β”œβ”€ Include full response payload
└─ Be queryable for compliance

CREATE TABLE integration_audit_trail (
id BIGSERIAL PRIMARY KEY,
workflow_id UUID NOT NULL, -- Temporal workflow ID
integration_name VARCHAR(50), -- "wherefour", "p21", "netsuite"
operation VARCHAR(20), -- "GET", "POST", "PATCH", "DELETE"
endpoint VARCHAR(255), -- "/orders", "/customers/123"
status VARCHAR(20), -- "SUCCESS", "FAILURE", "RETRY"
request_payload JSONB, -- What we sent
response_payload JSONB, -- What we got back
error_message TEXT, -- If failed
attempted_at TIMESTAMP,
completed_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_audit_workflow_id ON integration_audit_trail(workflow_id);
CREATE INDEX idx_audit_integration ON integration_audit_trail(integration_name);
CREATE INDEX idx_audit_timestamp ON integration_audit_trail(created_at DESC);

EndpointMethodPurposeWhere UsedCache TTL
/customers?limit=100GETCustomer listDagster job24h
/customers/{id}GETCustomer detailsTemporal (on-demand)24h
/orders?limit=100GETOrders listDagster snapshot-
/orders/{id}GETOrder details + itemsTemporal (on-submit)5min
/inventory?limit=100GETInventory catalogDagster job7d
/inventory/search?q=XGETSearch productsWebapp route-
/vendors?limit=100GETVendor listDagster job7d
/settings/price_tiersWEB SCRAPEPricing tiersDagster + Playwright24h
  • ❌ POST to /orders
  • ❌ PATCH to /order/{id}
  • ❌ DELETE to /order/{id}

Reason: No sandbox account available. Production data protection.

  • βœ… All GET endpoints verified (11/11 passing)
  • βœ… Order structure validated (order_items + custom_line_items)
  • βœ… Pricing data verified (subtotal, discount, total)
  • βœ… Search endpoints confirmed working
  • ❌ Write operations: NOT TESTED (blocked by safety rule)

ScenarioWithout TemporalWith Temporal
Network fails mid-submit❌ Data lostβœ… Retry automatically
Worker crashes❌ Workflow lostβœ… Resumed on recovery
Invalid data submitted❌ Silent failureβœ… Logged + auditable
Need to replay transaction❌ Manual interventionβœ… Replay from workflow ID
Compliance audit❌ No trailβœ… Full audit log
Production issue❌ Hard to debugβœ… Queryable workflow history

  1. βœ… WhereFour CK POC: Implement Temporal workflow for order submission (GET only)
  2. πŸ”² P21 Refactor: Create detailed project plan using this pattern (Q2/Q3)
  3. πŸ”² NetSuite: Update project plan to use Temporal (when started)
  4. πŸ”² SAP: Update project plan to use Temporal (when started)
  5. πŸ”² QuickBooks: Update project plan to use Temporal (when started)

Do not start P21/NetSuite/SAP refactors yet. This establishes the pattern for when you do.


Before implementing any integration write operations:

  • Sandbox account requested and confirmed
  • Test data loaded into sandbox
  • Temporal workflow created with retry logic
  • Audit trail table created
  • Rollback strategy documented
  • Error handling tested
  • Production credentials secured (1Password / Infisical)
  • Approval from product owner
  • Gradual rollout plan created (5% β†’ 25% β†’ 100%)

FINAL WARNING: 🚨 NO WRITES TO WHEREFOUR PRODUCTION 🚨
Sandbox account only. GET operations only. Await explicit approval before enabling POST/PATCH/DELETE.