บทที่ 21 · Part 4 — Boundaries, Data and Integration

Reliable Cross-Boundary Workflows

วาง idempotency, outbox/inbox, retry, Saga, Process Manager และ reconciliation เพื่อรับ crash และผลลัพธ์กำกวม

Reliable Cross-Boundary Workflows

Checkout บันทึก Payment succeeded แล้ว process crash ก่อนส่ง command ให้ Wallet หาก retry สร้าง provider charge ใหม่ ลูกค้าถูกเรียกเก็บซ้ำ หากไม่ retry Wallet ไม่ได้รับ credit นี่ไม่ใช่ปัญหาที่ database transaction ก้อนเดียวแก้ได้ เพราะ provider, MySQL และ message broker ไม่อยู่ atomic boundary เดียวกัน

จบบทนี้คุณจะ

วาง idempotency ตั้งแต่ business operation ถึง consumer ใช้ outbox/inbox, retry budget, Saga/Process Manager, reconciliation และ durable orchestration แยก compensation จาก rollback พร้อมออกแบบ recovery จาก crash และ ambiguous provider result โดยไม่ double-move money

[!IMPORTANT] วิธีอ่านหลักฐานในบทนี้ คำที่เป็น Definition/Pattern มีแหล่งต้นทางอยู่ใกล้ claim ส่วน decision table, starting structure, checklist และ lab เป็น Heuristic/Trade-off หรือ Course Convention สำหรับฝึกตัดสินใจ ไม่ใช่มาตรฐานสากล เว้นแต่บทจะระบุแหล่งและขอบเขตไว้ชัดเจน

ระบุทุก Idempotency Boundary

idempotent ต้องระบุ effect และ key:

BoundaryLogical keyProtected effect
Client/APIrequest/operation IDcreate one Checkout attempt
Provider adaptersame operation mapped to vendor keyone provider mutation
Local writeunique operation IDone state transition/record
Outboxevent ID tied to transactiondurable intent to publish
Consumer inboxproducer/event IDone handled delivery
Wallettop-up/business referenceone financial credit

Key ของแต่ละชั้นสัมพันธ์แต่ไม่จำเป็นต้องเหมือนกัน เก็บ correlation mapping และ outcome เพื่อ retry คืนผลเดิมได้ อย่าใช้ random key ใหม่ทุก attempt

ปัญหา Dual Write และ Outbox

ถ้า application COMMIT payment แล้ว publish message แยก มี crash gap Outbox เก็บ state กับ message intent ใน local transaction เดียว:

BEGIN;
UPDATE payment_operations
SET status = 'SUCCEEDED', version = version + 1
WHERE operation_id = ? AND version = ?;

INSERT INTO outbox(event_id, aggregate_id, event_type, payload, created_at)
VALUES (?, ?, 'PaymentSucceededV1', ?, ?);
COMMIT;

Relay publish outbox แล้ว mark sent มันอาจ publish ซ้ำหาก crash ระหว่าง 2 ขั้น ดังนั้น consumer ยังต้อง idempotent Inbox ใช้แนวคิดกลับกัน: record delivery/effect atomically ใน owner store

Outbox ไม่ทำให้ schema ภายในเป็น public contract ต้องมี explicit integration event mapper, version, owner และ deprecation

Retry ตาม Failure Mode

Retry เฉพาะ operation ที่ safe ตาม contract:

  • validation/permission/decline: ไม่ retry แบบ blind
  • overload/transient unavailable: exponential backoff + jitter + budget
  • timeout ก่อนรู้ effect: retry ด้วย same idempotency identity หรือ query
  • unknown provider effect: reconcile/query ไม่สร้าง operation ใหม่
  • permanent contract/config error: alert/operator action

AWS Builders' Library อธิบาย timeout, retry, backoff และ jitter รวมถึงปัญหา retry amplification กำหนด retry ที่ 1 orchestration layer และมี deadline/budget ไม่ให้หลาย client/service retry ซ้อน

Retry เงินต้องผูกกับ logical operation เดิม

การเปลี่ยน idempotency key หรือสร้าง command ใหม่หลัง timeout อาจ double charge/credit Persist intent และ key ก่อน side effect แล้ว query/reconcile ambiguous outcome ตาม contract

Saga, Process Manager และ Compensation

Saga ต้นฉบับอธิบาย long-lived transaction เป็นชุด local transactions กับ compensations ในระบบ services คำนี้ถูกใช้กับ choreography/orchestration หลากหลาย ต้องระบุ semantics

Process Manager ถือ workflow state/correlation และส่ง commands ตาม events เหมาะเมื่อ journey มี branch/timeout/recovery ที่ต้องเห็นชัด

Compensation เป็น business action เช่น void authorization, issue refund หรือ create adjustment ไม่ใช่ย้อน database เวลา และอาจล้มเหลว/ต้องอนุมัติเอง

Reconciliation เปรียบเทียบ authoritative records ข้าม boundary เพื่อหา missing/ambiguous effects และสร้าง repair task มันไม่ใช่ fallback ที่ละเลยได้ แต่เป็น control สำคัญของ money flow

Durable orchestration เช่น Temporal เก็บ workflow progress/timers/retries แต่ไม่แทน domain idempotency, authorization หรือ provider contract ดูรายละเอียดที่ Temporal Saga และ Production

ไล่เหตุการณ์ Crash Recovery

ออกแบบ top-up:

  1. Create local operation REQUESTED + stable ID
  2. Call provider ด้วย mapped idempotency key
  3. ถ้าผล unknown บันทึก UNKNOWN, schedule query/reconciliation
  4. ถ้า success บันทึก SUCCEEDED + WalletCreditRequested outbox ใน transaction
  5. Relay ส่ง message อาจซ้ำ
  6. Wallet unique (wallet_id, topup_operation_id) และบันทึก credit + inbox atomically
  7. Wallet publish WalletCredited
  8. Process Manager ปิด journey หรือ alert หาก age เกิน policy

ถ้า crash หลัง provider success ก่อน local save ขั้น 3/repair query ใช้ operation key หา outcome ถ้า crash หลัง publish ก่อน mark sent ขั้น 6 deduplicate effect

แบบฝึกปฏิบัติ: Failure Matrix

กรอกทุก boundary:

Step / owner:
Durable state before call:
Idempotency key and uniqueness:
Possible ambiguous result:
Retryable conditions and budget:
Recovery query/reconciliation:
Compensation business action:
Operator visibility and SLA owner:

inject failures หลังทุก durable/remote step แล้วตอบว่า replay จากตรงไหน Observable state ใดพิสูจน์ว่าเงินไม่ขยับซ้ำ ห้ามตอบเพียง “queue retry”

รายการตรวจสอบ

  • Idempotency ระบุ key + protected effect ต่อ boundary
  • Outbox/inbox ยอมรับ duplicate delivery
  • Integration event แยกจาก internal schema
  • Retry จำแนก terminal/transient/unknown และมี budget
  • Compensation เป็น explicit business action
  • Reconciliation มี owner, evidence และ repair path
  • Durable orchestrator ไม่แทน domain/datastore controls
  • Crash point ทุกจุดมี deterministic recovery story

สรุปบทนี้

Cross-boundary workflow เชื่อถือได้จาก durable intent, stable identity, idempotent effects, explicit state และ repair loops ไม่ใช่จาก happy-path sequence Outbox ปิด local dual write แต่ยังต้องมี consumer dedupe Retry ต้องรู้ uncertainty และ reconciliation เป็นส่วนหนึ่งของ design

อ่านเพิ่มเติม