+1

CẶP BÀI TRÙNG SPRING BOOT & TEMPORAL

Nếu Spring Boot là chiếc khiên 🛡️ vững chãi, giúp chúng ta dựng lên các Service mạnh mẽ, chuẩn chỉ...

Thì Temporal.io chính là cây quyền trượng 🪄 đầy quyền năng, giúp điều phối (Orchestrate) các luồng nghiệp vụ phức tạp, biến những đoạn code "dễ vỡ" trở nên "bất tử".

Tại sao lại nói Orchestrators là tương lai của Java (đặc biệt là Microservices)? Bởi vì chúng ta đã quá mệt mỏi với việc xử lý lỗi thủ công (manual retry), quản lý transaction phân tán (saga pattern) hay ác mộng "lạc trôi" message trong Kafka/RabbitMQ rồi. Temporal sinh ra để bạn code logic nghiệp vụ như code tuần tự (synchronous), còn việc "nhớ trạng thái", "thử lại khi lỗi" cứ để nó lo.

🔥 3 USE-CASE "KINH ĐIỂN" DỄ HÌNH DUNG NHẤT:

1️⃣ Đặt món ăn (Food Delivery App) 🍔

Vấn đề: Khách đặt món -> Trừ tiền -> Báo nhà hàng -> Tìm tài xế. Nếu bước "Tìm tài xế" thất bại sau 15 phút, làm sao hoàn tiền và hủy đơn nhà hàng tự động mà không bị sót?

Giải pháp: Temporal coi cả quy trình là 1 Workflow. Nếu lỗi ở bước nào, nó tự động kích hoạt quy trình bù trừ (Compensation) để hoàn tiền ngay lập tức. Code ngắn gọn, không cần check DB liên tục.

@WorkflowInterface
public interface OrderWorkflow {
    @WorkflowMethod
    void processOrder(String orderId, double amount);
}

public class OrderWorkflowImpl implements OrderWorkflow {
    
    // Khai báo các Activity
    private final OrderActivities activities = Workflow.newActivityStub(OrderActivities.class,
            ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(60)).build());

    @Override
    public void processOrder(String orderId, double amount) {
        // Khởi tạo Saga để quản lý các bước bù trừ
        Saga saga = new Saga(new Saga.Options.Builder().setParallelCompensation(false).build());

        try {
            // Bước 1: Trừ tiền (Charge Card)
            activities.chargeCard(orderId, amount);
            // Đăng ký ngay hành động bù trừ: Nếu sau này lỗi -> Hoàn tiền
            saga.addCompensation(activities::refundCard, orderId, amount);

            // Bước 2: Báo nhà hàng làm món (Reserve Kitchen)
            activities.reserveKitchen(orderId);
            // Đăng ký bù trừ: Nếu sau này lỗi -> Hủy bếp
            saga.addCompensation(activities::cancelKitchenOrder, orderId);

            // Bước 3: Tìm tài xế (Assign Driver)
            // Giả sử bước này bị lỗi (không tìm thấy xe) -> Exception ném ra
            activities.assignDriver(orderId);

            // Nếu mọi thứ OK -> Gửi thông báo thành công
            activities.notifyUser(orderId, "Tài xế đang đến!");

        } catch (Exception e) {
            // Khi có lỗi xảy ra ở bất kỳ đâu, kích hoạt Saga
            // Nó sẽ tự động chạy ngược: Hủy bếp -> Hoàn tiền
            saga.compensate();
            
            activities.notifyUser(orderId, "Đặt món thất bại, đã hoàn tiền!");
            throw e;
        }
    }
}

2️⃣ Đăng ký gói thành viên (Subscription) 📅

Vấn đề: User đăng ký dùng thử 30 ngày. Sau 30 ngày phải tự động trừ tiền.

Cách cũ: Chạy Cronjob quét DB mỗi đêm (rất nặng và dễ sót).

Cách Temporal: Viết lệnh Sleep(30 days). Quy trình sẽ "ngủ đông" đúng 30 ngày (không tốn RAM server), sau đó tự "tỉnh dậy" và thực hiện trừ tiền. Đơn giản đến ngỡ ngàng!

@WorkflowInterface
public interface SubscriptionWorkflow {
    @WorkflowMethod
    void startSubscription(String userId);
}

public class SubscriptionWorkflowImpl implements SubscriptionWorkflow {

    private final BillingActivities activities = Workflow.newActivityStub(BillingActivities.class,
            ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(60)).build());

    @Override
    public void startSubscription(String userId) {
        // Giai đoạn dùng thử (Trial)
        activities.sendWelcomeEmail(userId);

        // Workflow "ngủ" đúng 30 ngày. 
        // Trong 30 ngày này, Server có restart cũng không ảnh hưởng.
        Workflow.sleep(Duration.ofDays(30));

        // Sau 30 ngày, bắt đầu vòng lặp trừ tiền hàng tháng
        while (true) {
            try {
                activities.chargeMonthlySubscription(userId);
                activities.sendInvoiceEmail(userId);
                
                // Chờ tiếp 30 ngày cho kỳ thanh toán sau
                Workflow.sleep(Duration.ofDays(30));
                
            } catch (Exception e) {
                // Nếu thẻ lỗi, có thể gửi email nhắc nhở user cập nhật thẻ
                // Rồi chờ vài ngày thử lại, logic tùy biến rất linh hoạt
                activities.notifyPaymentFailed(userId);
                break; // Hoặc sleep chờ user update thẻ
            }
        }
    }
}

3️⃣ Chuyển tiền ngân hàng (Money Transfer) 💸

Vấn đề: Chuyển từ A sang B. Mạng lag, timeout. Tiền A đã trừ nhưng B chưa nhận.

Giải pháp: Temporal đảm bảo tính năng "Reliability". Nó sẽ retry kết nối đến ngân hàng B mãi mãi (hoặc theo cấu hình) cho đến khi thành công. Không bao giờ có chuyện mất tiền giữa đường.

@WorkflowInterface
public interface MoneyTransferWorkflow {
    @WorkflowMethod
    void transfer(String fromAccount, String toAccount, double amount, String transactionId);
}

public class MoneyTransferWorkflowImpl implements MoneyTransferWorkflow {

    // Cấu hình Retry cực mạnh
    private final ActivityOptions options = ActivityOptions.newBuilder()
            .setStartToCloseTimeout(Duration.ofSeconds(10)) // Timeout cho 1 lần gọi
            .setRetryOptions(RetryOptions.newBuilder()
                    .setInitialInterval(Duration.ofSeconds(1)) // Chờ 1s rồi thử lại
                    .setMaximumInterval(Duration.ofSeconds(100)) // Tăng dần thời gian chờ tối đa 100s
                    .setBackoffCoefficient(2.0) // Thời gian chờ x2 sau mỗi lần lỗi
                    .setMaximumAttempts(Integer.MAX_VALUE) // Thử lại vô hạn lần (đến khi bank sống lại)
                    .build())
            .build();

    private final AccountActivities activities = Workflow.newActivityStub(AccountActivities.class, options);

    @Override
    public void transfer(String fromAccount, String toAccount, double amount, String transactionId) {
        
        // Bước 1: Trừ tiền người gửi (Debit)
        activities.debit(fromAccount, amount, transactionId);

        // Bước 2: Cộng tiền người nhận (Credit)
        // Nếu API ngân hàng người nhận bị sập (System Down), 
        // dòng code này sẽ tự động Retry theo cấu hình trên. 
        // Workflow sẽ treo ở đây cho đến khi chuyển thành công.
        // Không cần viết vòng lặp while hay try-catch thủ công.
        activities.credit(toAccount, amount, transactionId);
    }
}

💡 Anh em đã thử combo Spring Boot + Temporal này chưa? Hay vẫn đang trung thành với Choreography qua Message Broker?


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí