Chào mừng đến với Tech Radar tuần này. Trong số trước, chúng ta đã khám phá Kratos Clean Architecture & Dapr Pub/Sub. Hôm nay, chúng ta sẽ giải quyết lĩnh vực phức tạp nhất của hệ thống phân tán: Điều phối Trạng thái (Stateful Orchestration). Chúng ta sẽ mổ xẻ cách triển khai Dapr Workflows và mô hình Actor bên trong Kratos.

Trước khi đi sâu vào code, hãy cùng điểm qua những tin tức nóng hổi trong 72 giờ qua.


1. Điểm Tin Radar: Dapr v1.18 & KubeCon India 2026

Answer-first: 72 giờ qua mang đến những thay đổi lớn. Dapr v1.18 ra mắt tính năng WorkflowAccessPolicy để kiểm soát bảo mật workflow nghiêm ngặt, OpenTelemetry chính thức “tốt nghiệp” CNCF tại KubeCon India, và Go 1.26.4 được phát hành. Trong khi đó, Kubernetes 1.33 sẽ hết hạn hỗ trợ (EOL) vào ngày 28/06.

Dapr v1.18: Cột mốc Bảo mật

Phát hành vào giữa tháng 6/2026, Dapr 1.18 đã khắc phục triệt để một lỗ hổng bảo mật lớn của workflow. Trước đây, bất kỳ caller nào trong cùng một trust domain đều có thể lên lịch hoặc hủy một workflow. Custom Resource Definition (CRD) mới WorkflowAccessPolicy cho phép bạn lập danh sách trắng (whitelist) rõ ràng những app-id cụ thể nào mới được phép kích hoạt API workflow Kratos của bạn.

Cập nhật CNCF & Go

  • KubeCon India 2026: AI-Native Scheduling thống trị các cuộc thảo luận. Quan trọng hơn đối với các lập trình viên enterprise, OpenTelemetry chính thức tốt nghiệp, củng cố vị thế là tiêu chuẩn không thể tranh cãi cho tracing (kết hợp tự nhiên với tích hợp Kratos của chúng ta bên dưới).
  • Go 1.26.4: Bản vá ổn định mới nhất đã ra mắt. Các team đang sử dụng bộ gom rác “Green Tea” mới nên cập nhật ngay lập tức.
  • K8s 1.33 EOL: Nếu cluster của bạn vẫn đang ở Kubernetes 1.33, bạn chỉ có hạn đến 28/06/2026 để nâng cấp.

2. Dapr Workflows vs. Choreography

Answer-first: Dapr Workflows cung cấp cơ chế điều phối tập trung, có trạng thái, được xây dựng trên engine durabletask-go, tự động lưu trữ trạng thái ở mỗi bước. Điều này thay thế cho mô hình event-driven choreography mỏng manh bằng một hàm Go duy nhất, dễ đọc, có thể sống sót qua các sự cố sập sidecar hoặc đứt gãy mạng.

Vấn Đề với Event Choreography

Khi triển khai một quy trình nhiều bước (ví dụ: Đặt hàng -> Thanh toán -> Tồn kho) bằng Pub/Sub choreography, logic bị phân tán qua nhiều service. Việc xử lý lỗi trở thành một cơn ác mộng với các sự kiện bù đắp (compensating events) và hàng đợi dead-letter.

Giải pháp Workflow

Dapr Workflows tập trung logic này vào một hàm “Workflow Orchestrator” và các hàm “Activity” thuần túy. Engine sẽ chạy lại (replay) hàm orchestrator để khôi phục trạng thái, nghĩa là orchestrator phải hoàn toàn mang tính tất định (deterministic). Không được phép gọi mạng, random số, hay ghi database trong orchestrator — tất cả các side-effects (tác vụ sinh ra tác dụng phụ) phải diễn ra bên trong các Activity.


3. Saga Pattern & Cơ Chế Bù Đắp (Compensation) trong Go

Answer-first: Để triển khai Saga trong Dapr Workflows, hãy sử dụng các khối if err != nil tiêu chuẩn của Go để bắt lỗi Activity, sau đó gọi rõ ràng các Activity bù đắp theo thứ tự ngược lại. Dapr không tự động rollback logic nghiệp vụ của bạn.

Khi một activity ở luồng dưới thất bại, bạn phải hoàn tác các activity đã thành công ở luồng trên. Đây là pattern chuẩn cho một orchestrator ở lớp biz của Kratos:

func OrderSaga(ctx *workflow.WorkflowContext) (any, error) {
    var input OrderInput
    if err := ctx.GetInput(&input); err != nil { return nil, err }

    // 1. Giữ tiền Thanh toán
    var paymentID string
    if err := ctx.CallActivity(ReservePayment, workflow.WithActivityInput(input)).Await(&paymentID); err != nil {
        return nil, err
    }

    // 2. Giữ Hàng tồn kho (Nếu lỗi, phải hoàn tiền Thanh toán)
    if err := ctx.CallActivity(ReserveInventory, workflow.WithActivityInput(input)).Await(nil); err != nil {
        ctx.CallActivity(ReleasePayment, workflow.WithActivityInput(paymentID)).Await(nil)
        return nil, fmt.Errorf("lỗi tồn kho: %w", err)
    }

    return "Saga Hoàn tất", nil
}

4. Tích hợp Kratos Clean Architecture

Answer-first: Không được phép để rò rỉ Dapr Go SDK vào lớp biz của Kratos. Lớp biz chỉ được chứa các định nghĩa workflow và interface thuần Go. Việc thực thi client.StartWorkflow của Dapr phải được triển khai ở lớp data và inject thông qua Wire.

Ánh Xạ Phân Lớp Chuẩn Xác

  • api: Định nghĩa Protobufs để kích hoạt workflow qua gRPC/HTTP.
  • service: Ánh xạ request đầu vào tới usecase của biz. Đăng ký các HTTP handler của Actor bằng daprd.NewService().
  • biz: Chứa logic OrderSaga và interface WorkflowRunner.
  • data: Import github.com/dapr/go-sdk/client và triển khai interface WorkflowRunner.

Cảnh Báo Lỗ Hổng AI: Các công cụ AI (như ChatGPT) thường xuyên “ảo giác” ra một module kratos/v2/transport/dapr. Thứ này không hề tồn tại. Hơn nữa, AI thường tự chèn dapr.SetCustomStatus(ctx) vào code Go, nhưng Go SDK không có tính năng trạng thái tùy chỉnh native (Issue #635). Bạn phải sử dụng trực tiếp Dapr State Store bên trong một Activity để lưu trữ trạng thái tiến trình tùy chỉnh.


5. Luồng Nâng Cao: External Events & Child Workflows

Answer-first: Đối với các quy trình cần sự phê duyệt của con người, sử dụng ctx.WaitForExternalEvent để tạm dừng workflow an toàn vào State Store với lượng RAM tiêu thụ bằng 0. Đối với các Saga khổng lồ, hãy chia nhỏ chúng bằng ctx.CallChildWorkflow để duy trì khả năng đọc và kiểm soát phiên bản độc lập.

Phê Duyệt Bởi Con Người

Thay vì các vòng lặp polling phức tạp, Dapr cho phép một workflow ngủ đông vô thời hạn cho đến khi một lệnh gọi REST API đánh thức nó.

// Tạm dừng workflow. Giải phóng RAM. Trạng thái lưu vào Redis.
var approved bool
err := ctx.WaitForExternalEvent("ManagerApproval", time.Hour*48).Await(&approved)

Để tiếp tục, một hệ thống bên ngoài chỉ cần tạo một HTTP POST tới endpoint raiseEvent của Dapr, nhắm mục tiêu vào instance workflow này.


6. Actor Concurrency, Reentrancy & Mở Rộng Quy Mô

Answer-first: Dapr Actors chạy đa nhiệm hoàn toàn đơn luồng (truy cập theo lượt), loại bỏ nhu cầu sử dụng sync.Mutex trong code Go của bạn. Tuy nhiên, điều này gây ra deadlocks nếu Actor A gọi Actor B, rồi B gọi ngược lại Actor A. Để sửa lỗi này, bạn phải bật tính năng Reentrancy (Tái nhập).

Bật Reentrancy trong Go

Khác với các SDK khác, Go SDK yêu cầu bạn mở một HTTP endpoint GET /dapr/config từ service Kratos của bạn, trả về một JSON object ActorReentrancyConfig. Kết hợp điều này với việc thiết lập reentrancy: { enabled: true } trong YAML Dapr Component.

Mở rộng trên Production

Trong Kubernetes, Dapr sử dụng Placement Service để băm (hash) và phân phối đồng đều các Workflow và Actor instances trên các pod ứng dụng của bạn.

  • Quy tắc Cốt Lõi: Bạn phải triển khai dịch vụ Placement và Scheduler của Dapr ở chế độ Tính sẵn sàng Cao (HA) (dapr_placement.ha=true).
  • State Store: Không bao giờ sử dụng SQLite cho các workflow phân tán trên production; cơ chế khóa file của nó sẽ trở thành nút thắt cổ chai. Bắt buộc phải dùng Redis.

7. Q&A: Những Cạm Bẫy Thực Tế

Làm thế nào để unit test Dapr Workflows trong Go?
Đừng cố gắng mock Dapr sidecar. Vì các Activity và Workflow được viết dưới dạng các hàm thuần Go trong lớp biz, bạn nên viết Unit Test Go tiêu chuẩn cho chúng bằng framework test durabletask-go. Sử dụng dapr run ở local để test tích hợp (integration testing) toàn diện.
OpenTelemetry tracing hoạt động như thế nào giữa Kratos và Dapr Actors?
Hoàn hảo. Dapr sử dụng header traceparent chuẩn của W3C. Hãy đảm bảo ứng dụng Kratos của bạn sử dụng middleware tracing.Server(). Kratos sẽ trích xuất trace context, và khi bạn truyền context.Context đó cho Dapr SDK, sidecar sẽ tự động truyền trace đó qua tất cả các workflow activities và child actors.
Tôi có thể cập nhật code Workflow sau khi các instance đã bắt đầu chạy không?
Phải cực kỳ cẩn thận. Vì orchestrator replay lại lịch sử, việc thay đổi thứ tự CallActivity trong một bản cập nhật sẽ làm hỏng các workflow đang chạy do lịch sử không tất định (non-deterministic). Bạn phải sử dụng tính năng SDK IsPatched cho các thay đổi nhỏ, hoặc đổi tên hàm (ví dụ: OrderSagaV2) cho các thay đổi cấu trúc.
Điều gì xảy ra nếu tôi cần Optimistic Concurrency Control bên ngoài Actor lock?
Dùng ETags. Khi bạn đọc trạng thái qua Dapr client, nó trả về một ETag. Truyền ETag đó lại trong quá trình SaveState. Nếu một process khác đã thay đổi trạng thái, Dapr trả về 409 Conflict, cho phép code Go của bạn retry.

Tiếp tục chuỗi bài viết với các phân tích sâu về Microservices với Dapr và trọn bộ System Design Series.

📡 Số tiếp theo: Tech Radar 24/06 — K8s là hệ điều hành AI, GKE Hypercluster & Sự thống trị của Golang

📬 Nhận Tech Radar hàng tuần — không spam, chỉ signal: Đăng ký tại đây.


🤝 Kết nối với tôi

Bạn đang gặp phải những thách thức tương tự về kiến trúc hệ thống, mở rộng quy mô (scaling) hay dịch chuyển (migration)? Hãy kết nối với tôi trên LinkedIn, theo dõi GitHub của tôi, hoặc gửi một email để trao đổi nhé.