Những năm gần đây, từ hẻm nhỏ tới mặt tiền, người người nhà nhà đều nghe tiếng lành đồn xa và mong muốn được trải nghiệm nét đẹp của bông hồng "microservices". Mình cũng là một người dân sống trong con hẻm nhỏ kia, may mắn có cơ hội được trải nghiệm Microservices. Tuy nhiên, không ít lần mình ăn hành với yêu cầu về tính nhất quán, toàn vẹn dữ liệu của nó.
Vì sao ăn hành ? Khi làm việc với hệ thống monolithic hay một service, mình thao tác trên một database (db), đằng này với micro, mỗi service một db (database per service), việc đảm bảo tính nhất quán dữ liệu giữa các db với nhau khó khăn muôn trùng.
Saga pattern ra đời để giải quyết bài toán đó. Nếu ai đó hỏi bạn Saga pattern là gì ? Thì bạn có thể nói rằng Saga là một tập hợp transactions của các services liên kết với nhau để thực hiện một yêu cầu từ business. Trong quá trình thực hiện, nếu một transaction từ một service bị tèo thì tất cả sẽ được trả lại trạng thái ban đầu.
Nghe cồng kềnh quá, mình xin lấy một ví dụ quốc dân từ chức năng tạo 1 đơn hàng của hệ thống e-commerce để minh họa.
Khi user tạo 1 đơn hàng, Order service sẽ ghi mới record vào db, tiếp đến Inventory service sẽ đi update số lượng tồn kho, cuối cùng Shipment service sẽ tạo đơn vận chuyển. Chuyện sẽ không có gì tổn thương nếu đứt gánh giữa đường, xui cái shipment service tèo mà không rollback lại dữ liệu thì toang cực mạnh. Saga mang đến hai liều thuốc trị thương cực kì hiệu quả: Choreography-based saga và Orchestration-based saga. Vậy sự khác biệt giữa 2 liều thuốc này là gì ?
- Choreography: các services tự thương lượng với nhau.
- Orchestration: có chủ tọa đứng ra giải quyết
Saga đáp ứng được nguyên lý ACDs (thiếu I)
- Atomicity: dữ liệu từ một service tèo là ngủm toàn hệ thống, rollback lại trạng thái ban đầu
- Consistency: dữ liệu của toàn hệ thống giữa các services được nhất quán
- Durability: chủ tọa ngủm (Orchestrator service) thì có thể backup lại event từ message-broker (kafka)
Tiếp theo mình sẽ dùng Orchestration-based saga để dựng ứng dụng minh họa cho tính năng tạo order đơn hàng (ví dụ quốc dân bên trên).
Mình sẽ sử dụng Kafka làm message-broker, xây dựng trên Kafka confluent cloud free bao gồm 3 topic chính tương ứng với 3 services:
- Order Service
- Inventory Service
- Shipment Service
async function runTransaction() {
const sagaBuilder = new SagaBuilder()
.route('ORDER_SERVICE')
.invoke(async () => {
await axios.post('http://order-service/api/v1/orders', seedProduct)
})
.rollBack(async () => {
await axios.put('http://order-service/api/v1/orders/2', { status: 'INACTIVE' })
})
.route('INVENTORY_SERVICE')
.invoke(async () => {
await axios.put('http://inventory-service/api/v1/products/1', { type: 'INVOKE' })
})
.rollBack(async () => {
await axios.put('http://inventory-service/api/v1/products/1', { type: 'ROLLBACK' })
})
.route('SHIPMENT_SERVICE')
.invoke(async () => {
// console.log('Create shipment...');
throw new Error('Something wrong')
})
.rollBack(async () => {
console.log('Roll back to Shipment Service API');
});
const sagaProcessor = await sagaBuilder.build()
sagaProcessor.start({ id: 1 })
}
Khi bắt đầu run transaction, orchestrator sẽ điều phối đến từng topic message, sau đó sẽ đi trigger dữ liệu của service tương ứng, nếu trong quá trình thực hiện bị fail ở một service nào đó thì hệ thống sẽ rollback lại dữ liệu như ban đầu.
Github repo: https://github.com/lequocduyquang/saga-microservices
Bên trên là những hiểu biết mà mình đúc kết và trải nghiệm khi được va chạm với Saga pattern. Bài viết chắc chắn còn rất nhiều thiếu sót, mình hi vọng được mọi người đóng góp và chia sẻ để mình có thể học hỏi và cải thiện. Xin chân thành cảm ơn mọi người đã dành thời gian đọc bài viết của mình.
Nguồn tham thảo
- Blog: Saga - Microservices.io
- Book: Microservices Pattern - Chris Richardson - Chapter Saga