T3 — Choreography based Saga Pattern - congsinhv/fluxion GitHub Wiki

Choreography-based Saga Pattern trong Hệ Thống MDM

Issue: #13 — Nghiên cứu Choreography-based Saga Pattern Tuần: 3 | 07/04 – 13/04/2026 Numbering chính thức: Mục 2.5 theo Master TOC (Chương 2)


2.5.1 Vấn Đề Giao Dịch Phân Tán trong Microservices

Trong các hệ thống monolithic truyền thống, các giao dịch ACID (Atomicity, Consistency, Isolation, Durability) được quản lý bởi một cơ sở dữ liệu duy nhất, đảm bảo tính toàn vẹn thông qua cơ chế khóa và rollback tự động. Tuy nhiên, khi chia tách ứng dụng thành microservices, mỗi dịch vụ quản lý cơ sở dữ liệu độc lập, làm cho giao dịch ACID xuyên dịch vụ trở nên không khả thi [3].

Không có cơ chế "rollback toàn cầu" trong hệ thống phân tán. Đây là vấn đề mà Saga Pattern giải quyết — cơ chế quản lý giao dịch dài, phân tán bằng chuỗi giao dịch cục bộ với compensating transactions [3].

Áp dụng trong Fluxion: action-trigger Lambda tạo command record trong RDS → ios-process-action Lambda gửi APNS push đến thiết bị → Nếu thiết bị không phản hồi, cần hoàn nguyên — không có cơ chế "rollback toàn cầu" giữa các Lambda và ECS service.


2.5.2 Định Nghĩa Saga Pattern

2.5.2.1 Khái Niệm Ban Đầu (Garcia-Molina & Salem, 1987)

Saga Pattern được đề xuất bởi Hector Garcia-Molina và Kenneth Salem tại ACM SIGMOD 1987 [1]. Các tác giả định nghĩa Long Lived Transaction (LLT) — giao dịch kéo dài giữ tài nguyên lâu, làm chậm các giao dịch khác.

Giải pháp: một saga là chuỗi giao dịch T₁, T₂, ..., Tₙ với đảm bảo:

  • Hoặc tất cả Tᵢ hoàn thành thành công
  • Hoặc chuỗi compensating transactions C₁...Cₖ được thực thi để hoàn nguyên

2.5.2.2 Định Nghĩa Hiện Đại (Richardson, 2018)

Chris Richardson [3] định nghĩa lại Saga cho microservices cloud-native: chuỗi giao dịch cục bộ trong đó mỗi giao dịch cập nhật dữ liệu của dịch vụxuất bản sự kiện để kích hoạt bước tiếp theo. Nếu một bước thất bại, saga thực thi compensating transactions để hoàn nguyên.

Điểm khác biệt so với 1987:

  • Tập trung vào eventual consistency thay vì strong consistency
  • Sử dụng messaging/events làm cơ chế điều phối
  • Thích ứng với môi trường bất đồng bộ và cloud-native

2.5.3 So Sánh Orchestration vs Choreography

Saga có hai mô hình điều phối [3]:

Khía cạnh Orchestration Choreography
Điều phối bởi Orchestrator trung tâm Sự kiện giữa các dịch vụ
Mô hình giao tiếp Synchronous (request-reply) Asynchronous (pub-sub)
Coupling Tight (orchestrator biết tất cả) Loose (mỗi service biết input/output của mình)
Debugging Dễ (flow trong orchestrator) Khó hơn (flow phân tán)
Khả năng mở rộng Bị giới hạn bởi orchestrator Tốt (scale độc lập)
Single Point of Failure Orchestrator là SPoF Không có SPoF rõ ràng
Phù hợp serverless Không (cần coordinator dài hạn) Có (event-driven, stateless)

Bảng 2.5.1: So sánh Orchestration và Choreography-based Saga


2.5.4 Tại Sao Choreography Phù Hợp với Fluxion

2.5.4.1 Kiến Trúc Bất Đồng Bộ

Fluxion dùng SQS làm backbone pipeline lệnh — bản chất là bất đồng bộ. Không có orchestrator trung tâm; các dịch vụ trao đổi qua sự kiện, tương thích tự nhiên với choreography [3].

2.5.4.2 Không Có Orchestrator Tập Trung

Mỗi Lambda chỉ biết phần việc của mình:

  • action-trigger Lambda: nhận từ SQS, tạo record RDS, publish SNS
  • ios-process-action Lambda: nhận từ SQS, gửi APNS
  • checkin-ecs: nhận callback từ device, cập nhật FSM

Không có dịch vụ nào "biết" toàn bộ flow.

2.5.4.3 Loose Coupling và Độc Lập Triển Khai

Choreography cho phép các dịch vụ phát triển và triển khai độc lập [3]. Thêm subscriber mới (ví dụ: audit-logger) vào device-event-sns không cần thay đổi bất kỳ dịch vụ hiện có nào.

2.5.4.4 Phù Hợp với EDA

Fluxion đã dùng SNS/SQS theo EDA pattern (T2). Choreography là mô hình giao tiếp tự nhiên cho EDA [2] — mỗi service là event consumer + event producer.


2.5.5 Command Pipeline Fluxion Như Một Choreography Saga

2.5.5.1 Ánh Xạ Các Bước Sang Saga Participants

Bước Layer Service Giao dịch cục bộ State sau bước Sự kiện
1 BE action-resolver λ Validate (busy + transition) + enqueue SQS
2 BE action-trigger λ INSERT action_executions (ACTION_PENDING), UPDATE device ACTION_PENDING command-sns
3 OEM apns-sender λ Cache command + send APNS silent push
4 OEM mdm-handler λ Device polls → serve command from cache
5 iOS Device Execute command, send result to OEM
6 OEM mdm-handler λ Receive result, publish event checkin-sns
7 BE checkin-handler λ UPDATE action_executions + devices + INSERT milestones ACTION_COMPLETED

Architecture note: BE chỉ biết DB, OEM chỉ biết 3rd party (APNS + device). Giao tiếp qua SNS/SQS events. Xem chi tiết tại T5 — Command PipelineT4 — Kiến trúc tổng thể.

Bảng 2.5.2: Ánh xạ command pipeline sang Choreography Saga participants

2.5.5.2 Schema: action_executions

CREATE TABLE action_executions (
    id                  UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    device_id           UUID NOT NULL,
    assigned_action_id  UUID NOT NULL,
    state               VARCHAR(20) NOT NULL DEFAULT 'ACTION_PENDING',
    created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    ext_fields          JSONB
    -- ext_fields: { "failed_reason": "...", "retry_count": 2, "apns_message_id": "..." }
);

CREATE INDEX idx_ae_device_id ON action_executions(device_id);
CREATE INDEX idx_ae_assigned_action_id ON action_executions(assigned_action_id);
CREATE INDEX idx_ae_active_state ON action_executions(state)
    WHERE state NOT IN ('ACTION_COMPLETED', 'ACTION_FAILED');

2.5.5.3 Idempotency

Vì SQS cung cấp at-least-once delivery, các giao dịch phải idempotent [3]:

-- Safe (idempotent): chuyển ACTION_SENT → ACTION_FAILED
UPDATE action_executions
SET state      = 'ACTION_FAILED',
    updated_at = NOW(),
    ext_fields = jsonb_set(COALESCE(ext_fields, '{}'), '{failed_reason}', '"device_timeout"')
WHERE id = 'exec-456' AND state = 'ACTION_SENT';
-- Lần thứ hai: state đã là 'ACTION_FAILED' → WHERE không match → no-op

2.5.6 Compensating Transactions và Chiến Lược Phục Hồi

2.5.6.1 Định Nghĩa

Giao dịch bù trừ là giao dịch đảo ngược hiệu quả của giao dịch đã commit. Richardson [3] yêu cầu: idempotentretryable.

Ví dụ:

  • Giao dịch ban đầu: INSERT INTO action_executions (state='ACTION_PENDING')
  • Compensating: UPDATE action_executions SET state='ACTION_FAILED' WHERE id=... AND state IN ('ACTION_PENDING','ACTION_SENT')

2.5.6.2 Kịch Bản Thất Bại

Kịch bản 1 — Device không phản hồi (timeout):

T=0s:  action-trigger: INSERT action_executions (ACTION_PENDING)
T=1s:  ios-process-action: gửi APNS → UPDATE ACTION_SENT
T=31s: Timeout — checkin-ecs không nhận callback
→ Compensating: UPDATE action_executions SET state='ACTION_FAILED',
                ext_fields='{"failed_reason":"device_timeout"}'
                WHERE id=... AND state='ACTION_SENT'
→ User notification: "Lock command failed — device offline"

Kịch bản 2 — APNS delivery failure:

T=0s:  action-trigger: INSERT action_executions (ACTION_PENDING)
T=1s:  ios-process-action: APNS returns 400 (BadDeviceToken)
→ Compensating: UPDATE action_executions SET state='ACTION_FAILED',
                ext_fields='{"failed_reason":"apns_bad_token"}'
                WHERE id=... AND state='ACTION_PENDING'
→ UPDATE devices SET ext_fields=jsonb_set(ext_fields, '{invalid_token}', 'true')

2.5.6.3 DLQ Retry Strategy

action-trigger-sqs
    → action-trigger Lambda
    ├─ Success → INSERT action_executions (ACTION_PENDING) + publish SNS
    └─ Exception → after maxReceiveCount → DLQ

action-trigger-dlq
    → DLQ handler Lambda
    ├─ Recoverable? → retry với exponential backoff → push back to primary queue
    └─ Non-recoverable? → UPDATE action_executions SET state=ACTION_FAILED + alert ops team

2.5.7 Finite State Machine (FSM) Cho Trạng Thái Lệnh

2.5.7.1 Định Nghĩa Trạng Thái

Trạng thái Owner Mô tả Chuyển tiếp sang
ACTION_PENDING action-trigger Lambda Action khởi tạo trong RDS, message enqueued tới ios-process-action ACTION_SENT, ACTION_FAILED
ACTION_SENT checkin-ecs Nhận event từ ios-process-action — APNS gửi thành công, chờ device apply ACTION_COMPLETED, ACTION_FAILED
ACTION_COMPLETED checkin-ecs Device đã apply action, callback received (terminal)
ACTION_FAILED Bất kỳ service Lỗi ở bất kỳ bước — APNS error, timeout, device error (terminal)

Bảng 2.5.3: action_executions FSM states trong Fluxion

2.5.7.2 FSM State Diagram

stateDiagram-v2
    [*] --> ACTION_PENDING: action-trigger\nINSERT action_executions

    ACTION_PENDING --> ACTION_SENT: checkin-ecs nhận event\nAPNS gửi thành công
    ACTION_PENDING --> ACTION_FAILED: ios-process-action\nAPNS error (BadToken, v.v.)

    ACTION_SENT --> ACTION_COMPLETED: checkin-ecs\nnhận device callback
    ACTION_SENT --> ACTION_FAILED: timeout — device\nkhông gửi callback

    ACTION_COMPLETED --> [*]
    ACTION_FAILED --> [*]

Hình 2.2: Finite State Machine cho action_executions lifecycle trong Fluxion


2.5.8 Sequence Diagram: Choreography Saga Flow

sequenceDiagram
    participant User as Người Dùng
    participant API as AppSync
    participant AR as action-resolver λ
    participant AT as action-trigger λ
    participant RDS as RDS PostgreSQL
    participant SNS1 as command-sns
    participant APNS_L as apns-sender λ (OEM)
    participant CACHE as Command Cache
    participant APNS as Apple APNS
    participant Device as iOS Device
    participant APIGW as API Gateway mTLS
    participant MDM as mdm-handler λ (OEM)
    participant SNS2 as checkin-sns
    participant CK as checkin-handler λ

    User->>API: mutation assignAction(deviceId, actionId)
    API->>AR: resolve

    Note over AR,AT: BE Layer — Validate + Create
    AR->>RDS: SELECT device, action (validate)
    AR->>AT: enqueue via SQS
    AT->>RDS: INSERT action_executions (ACTION_PENDING)
    AT->>RDS: UPDATE devices SET assigned_action_id
    AT->>SNS1: publish command {command_uuid, tokens, payload}

    Note over APNS_L,MDM: OEM Layer — MDM Protocol
    SNS1->>APNS_L: via SQS
    APNS_L->>CACHE: cache command
    APNS_L->>APNS: silent push {"mdm":"PushMagic"}
    APNS-->>Device: wake up
    Device->>APIGW: PUT /mdm (Idle)
    APIGW->>MDM: mTLS verified
    MDM->>CACHE: read + delete command
    MDM-->>Device: 200 {CommandUUID, Command}
    Device->>Device: execute command
    Device->>APIGW: PUT /mdm (Acknowledged)
    APIGW->>MDM: trigger
    MDM->>SNS2: publish {ACTION_COMPLETED, command_uuid}
    MDM-->>Device: 200 (empty)

    Note over CK: BE Layer — Update DB
    SNS2->>CK: via SQS
    alt ACTION_COMPLETED
        CK->>RDS: UPDATE action_executions SET status=ACTION_COMPLETED
        CK->>RDS: UPDATE devices SET current_policy_id, assigned_action_id=NULL
        CK->>RDS: INSERT milestones
        Note over RDS: ✓ Saga hoàn thành
    else ACTION_FAILED
        CK->>RDS: UPDATE action_executions SET status=ACTION_FAILED
        CK->>RDS: UPDATE devices SET assigned_action_id=NULL
        Note over RDS: ✗ Compensating — device state giữ nguyên
    end

    opt APNS failure
        APNS_L->>SNS2: publish {ACTION_FAILED, reason: apns_error}
        SNS2->>CK: via SQS
        CK->>RDS: UPDATE action_executions → ACTION_FAILED
    end

    opt Timeout
        Note over RDS: timeout-handler λ periodic check
        Note over RDS: mark stale ACTION_PENDING → ACTION_FAILED
    end

    Note over AT,ECS: Choreography Saga Ends

Hình 2.3: Sequence diagram Choreography Saga flow trong Fluxion


2.5.9 So Sánh Saga vs Two-Phase Commit (2PC)

Khía cạnh Saga (Choreography) Two-Phase Commit (2PC)
Tính nhất quán Eventual Consistency Strong Consistency (ACID)
Khóa tài nguyên Không có Giữ lâu dài (bottleneck)
Độ trễ Cao (async) Thấp (synchronous)
Khả năng mở rộng Cao (dịch vụ độc lập) Thấp (coordinator là SPoF)
Tolerant failure Tốt (DLQ retry) Yếu (timeout → blocked)
Rollback Compensating transactions Tự động (DB-level)
Phù hợp serverless ✓ Rất tốt ✗ Không phù hợp
Phù hợp async MDM ✓ Tối ưu ✗ Không phù hợp

Bảng 2.5.4: So sánh Saga Pattern và Two-Phase Commit

Lý do 2PC không phù hợp cho Fluxion:

  1. Blocking nature: 2PC khóa tài nguyên cho đến khi phase 2 hoàn thành. Device offline 30s → toàn bộ transaction bị block.
  2. Single Point of Failure: Coordinator bị sập → participants giữ khóa vô thời hạn.
  3. Asynchronous incompatibility: SQS, SNS, iOS callback là bất đồng bộ — 2PC cần synchronous dialogue.
  4. Lambda timeout: 2PC coordinator cần trạng thái dài hạn — không phù hợp Lambda (max 15 phút).

Garcia-Molina & Salem đã nhận thấy vấn đề này từ 1987 — Saga sinh ra để giải quyết chính xác vấn đề này [1].


2.5.10 Lợi Ích và Thách Thức

2.5.10.1 Lợi Ích

  • Loose coupling: Mỗi service chỉ biết sự kiện của mình, không biết toàn bộ saga [3]
  • Horizontal scalability: Scale độc lập per service, không chờ orchestrator
  • Natural EDA fit: SNS/SQS là AWS-managed, không cần quản lý orchestrator thủ công
  • Resilience: DLQ, retry, timeout built-in trong AWS services [4]
  • Eventual consistency: Phù hợp MDM — không cần strong consistency tức thì

2.5.10.2 Thách Thức

  • Distributed tracing: Debug flow phân tán khó hơn — cần AWS X-Ray với correlation ID [15]
  • Compensating transaction design: Phải thiết kế bù trừ cho từng bước; không tự động như ACID
  • Eventual consistency anomalies: Trong thời gian giữa các bước, dữ liệu có thể không nhất quán
  • Testing complexity: Phải mô phỏng failure scenarios và async behavior

2.5.11 Tổng Hợp: Ánh Xạ Lý Thuyết — Thực Tiễn Fluxion

Khái niệm lý thuyết Nguồn Biểu hiện trong Fluxion
Long Lived Transaction Garcia-Molina & Salem [1] MDM command pipeline (multi-step, async)
Compensating Transaction Garcia-Molina & Salem [1] UPDATE action_executions SET state=ACTION_FAILED
Choreography Saga Richardson [3] SQS/SNS event chain, không có orchestrator
Eventual Consistency Richardson [3] Device status cập nhật sau callback qua checkin-ecs
DLQ Retry Pattern Hohpe & Woolf [2] action-trigger-dlq + retry Lambda
Idempotency Richardson [3] WHERE state='ACTION_SENT' guard — no-op nếu đã FAILED
Circuit Breaker Nygard [15] Max retry count + ACTION_FAILED terminal state
FSM action_executions states: ACTION_PENDING→ACTION_SENT→ACTION_COMPLETED/ACTION_FAILED

Bảng 2.5.5: Ánh xạ lý thuyết — thực tiễn Saga trong Fluxion


Kết Luận

Choreography-based Saga Pattern giải quyết một trong những thách thức căn bản nhất của kiến trúc microservices: quản lý tính nhất quán dữ liệu xuyên dịch vụ mà không cần giao dịch phân tán (Distributed Transaction) hay điều phối trung tâm (Orchestrator). Nền tảng lý thuyết được đặt ra từ năm 1987 bởi Garcia-Molina & Salem với khái niệm Long Lived Transaction và Compensating Transaction vẫn giữ nguyên giá trị trong bối cảnh kiến trúc serverless hiện đại — minh chứng cho tính bền vững của giải pháp này.

Trong Fluxion, Choreography-based Saga là pattern phù hợp tự nhiên với pipeline lệnh MDM: mỗi bước (tạo command record, gửi APNS push, nhận callback từ thiết bị) là một giao dịch cục bộ độc lập, liên kết với nhau qua sự kiện SNS/SQS thay vì lời gọi đồng bộ. Khi thiết bị không phản hồi hoặc Lambda thất bại, compensating transaction (UPDATE action_executions SET state='ACTION_FAILED') đảm bảo hệ thống hội tụ về trạng thái nhất quán. Cơ chế DLQ (Dead Letter Queue) của AWS SQS đóng vai trò lưới an toàn cuối cùng, ngăn chặn mất mát sự kiện và cung cấp khả năng kiểm tra thủ công khi tất cả các lần thử lại đều thất bại.

Việc lựa chọn Choreography thay vì Orchestration phản ánh nguyên tắc kiến trúc cốt lõi của Fluxion: ưu tiên tách biệt hoàn toàn (loose coupling) và tránh tạo ra điểm thất bại đơn lẻ (Single Point of Failure). Mỗi Lambda service chỉ cần biết sự kiện mà nó tiêu thụ và sự kiện nó phát ra — không phụ thuộc vào sự tồn tại hay trạng thái của các service khác. Đây là nền tảng kỹ thuật cho phép Fluxion mở rộng quy mô theo chiều ngang (horizontal scaling) và duy trì khả năng hoạt động ngay cả khi một thành phần trong pipeline gặp sự cố.


Tài Liệu Tham Khảo

[1] Garcia-Molina, H. & Salem, K. (1987). Sagas. Proceedings of the ACM SIGMOD International Conference on Management of Data, pp. 249–259. https://dl.acm.org/doi/10.1145/38713.38742

[2] Hohpe, G. & Woolf, B. (2003). Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions. Addison-Wesley. https://www.enterpriseintegrationpatterns.com/

[3] Richardson, C. (2018). Microservices Patterns: With Examples in Java, Ch. 4 (Saga). Manning Publications. https://microservices.io/patterns/data/saga.html

[4] AWS. (2024). Saga Pattern — AWS Prescriptive Guidance: Cloud Design Patterns. https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/saga-choreography.html

[15] Nygard, M. (2018). Release It! 2nd Edition: Design and Deploy Production-Ready Software. The Pragmatic Programmers. https://pragprog.com/titles/mnee2/release-it-second-edition/


Câu hỏi còn mở:

  1. SNS ordering: SNS không đảm bảo thứ tự sự kiện — nếu CommandFailed đến trước CommandCreated (race condition hiếm), FSM xử lý thế nào?
  2. Manual compensation: Khi compensating transaction thất bại (DB down), cần quy trình phục hồi thủ công nào?
  3. Monitoring: Cần thiết kế dashboard theo dõi saga state qua nhiều services.