Interface Contracts Tập 4: Bảo vệ Hợp đồng: Contract Testing & Kỹ thuật Consumer-Driven.
Sau khi đã dùng Swagger hoặc Protobuf để "ký giấy tờ" với nhau, thực tế phũ phàng là: Giấy tờ không thể ngăn được lập trình viên gõ sai code.
Giảສົ Frontend và Backend đã chốt Swagger là API trả về trường transactionId. Ba tháng sau, một bạn dev Backend mới vào sửa code, thấy tên biến dài quá nên đổi thành id cho gọn. Unit Test của Backend vẫn pass xanh lè. Frontend thì không hề hay biết. Tới ngày gộp code (Integration) hoặc tệ hơn là lên Production... BÙM! Toàn bộ tính năng thanh toán tê liệt.
Làm sao để hệ thống tự động phát hiện ra "Backend vừa phá vỡ hợp đồng" ngay từ lúc code còn ở máy cá nhân? Đó là lúc Contract Testing (Kiểm thử Hợp đồng) và công cụ Pact tỏa sáng.
1. Hạn chế của các phương pháp Test truyền thống
Trước khi có Contract Testing, chúng ta hay dùng 2 cách để bắt lỗi này nhưng đều không triệt để:
- End-to-End (E2E) Test: Bật cả Frontend, Backend và Database lên rồi chạy test tự động. Rất chính xác nhưng chạy cực kỳ chậm, tốn tài nguyên và rất dễ lỗi vặt (flaky).
- Unit Test với Mock: Frontend tự tạo ra dữ liệu giả để test. Backend cũng tự tạo dữ liệu giả để test. Vấn đề là dữ liệu giả (Mock) do Frontend tự tưởng tượng ra có thể không còn khớp với thực tế Backend đang code.
2. Giải pháp: Consumer-Driven Contract Testing (CDCT)
Consumer-Driven (Được định hướng bởi người tiêu thụ) là một sự đảo ngược tư duy tuyệt vời. Thay vì Backend tự viết API rồi bắt Frontend theo, CDCT nói rằng: "Frontend (Consumer) hãy định nghĩa chính xác những gì bạn cần, và Backend (Provider) phải có nghĩa vụ đáp ứng đúng như thế."
Pact là công cụ nổi tiếng nhất thế giới để làm việc này. Quy trình diễn ra qua 3 bước:
Bước 1: Consumer (Frontend) soạn thảo Hợp đồng
Frontend sẽ viết một Unit Test. Trong test này, Frontend khai báo rõ kỳ vọng của mình.
// Đoạn code Frontend viết test bằng Pact (Mô phỏng)
const provider = new Pact({
consumer: 'Frontend_App',
provider: 'Payment_Service',
});
// Frontend định nghĩa: "Khi tôi gửi Request này..."
provider.addInteraction({
state: 'Tài khoản có đủ tiền',
uponReceiving: 'Một yêu cầu rút tiền',
withRequest: {
method: 'POST',
path: '/withdraw',
body: { amount: 100 }
},
// "...thì Backend PHẢI trả về Response đúng cấu trúc này!"
willRespondWith: {
status: 200,
body: {
transactionId: Matchers.like('TXN-123'), // Chỉ cần đúng kiểu String, không cần đúng giá trị
status: 'SUCCESS'
}
}
});
Khi Frontend chạy test này, Pact sẽ tự động sinh ra một file JSON (gọi là file Pact). Đây chính là "Bản hợp đồng vật lý".
Bước 2: Lưu trữ Hợp đồng vào Broker
File JSON này sẽ được đẩy lên một server trung tâm gọi là Pact Broker (như một phòng công chứng). Broker này lưu trữ mọi hợp đồng giữa tất cả các Microservices trong công ty.
Bước 3: Provider (Backend) xác minh Hợp đồng
Mỗi khi Backend sửa code và chạy CI/CD (trước khi deploy), hệ thống tự động kéo file Hợp đồng JSON từ Pact Broker về.
Pact sẽ đóng vai trò làm một "Frontend giả", tự động bắn request vào server Backend thật (hoặc chạy trong bộ nhớ), và kiểm tra xem kết quả trả về có khớp với hợp đồng không.
Nếu anh dev Backend đổi transactionId thành id, bài test ở phía Backend sẽ FAIL NGAY LẬP TỨC với thông báo:
"Lỗi! Bạn đã vi phạm hợp đồng với Frontend_App. Bọn họ đang cần trường transactionId."
Backend sẽ không thể deploy đoạn code lỗi đó lên server. Thảm họa được ngăn chặn!
3. Tại sao kỹ thuật này lại "Bá đạo"?
- Nhanh như Unit Test: Bạn không cần bật toàn bộ hệ thống lên. Frontend test độc lập. Backend test độc lập. Nhưng chúng ta vẫn chắc chắn 100% là hai bên sẽ giao tiếp mượt mà.
- Ngăn chặn lỗi "Over-fetching" (Làm thừa): Vì hợp đồng được thúc đẩy bởi Consumer, Backend chỉ cần code đúng những trường (fields) mà Frontend thực sự dùng. Không còn chuyện Backend trả về một cục JSON khổng lồ 50 trường dữ liệu mà Frontend chỉ dùng đúng 2 trường.
- Biết rõ ai đang dùng API của mình: Nếu Backend muốn xóa một API cũ, họ chỉ cần nhìn vào Pact Broker là biết ngay còn App Mobile cũ nào đang phụ thuộc vào API này hay không.
Tổng kết
Lời nói gió bay, Swagger cũng chỉ là tài liệu. Chỉ có Contract Testing (Pact) mới thực sự "khóa tay" các hệ thống lại với nhau, đảm bảo rằng không ai có thể đơn phương phá vỡ quy tắc giao tiếp mà không bị hệ thống bắt quả tang.
All rights reserved