1. Bài toán tích hợp trong kiến trúc microservices
Hãy tưởng tượng một nền tảng fintech với hàng chục dịch vụ nhỏ: dịch vụ ví, dịch vụ thanh toán, dịch vụ hạn mức tín dụng, dịch vụ thông báo. Ứng dụng di động (consumer) gọi API của dịch vụ ví (provider) để lấy số dư. Mỗi dịch vụ do một đội khác nhau phát triển, deploy độc lập, nhiều lần mỗi ngày. Khi số lượng cặp gọi nhau tăng lên, một thay đổi nhỏ ở phía provider — đổi tên một trường JSON, bỏ một field, thay kiểu dữ liệu — có thể làm hỏng consumer mà không ai phát hiện cho tới khi khách hàng gặp lỗi trên production.
Câu hỏi cốt lõi mà mỗi đội phải trả lời trước khi deploy là: 'Thay đổi của tôi có phá vỡ bất kỳ ai đang phụ thuộc vào tôi không?'. Trong một hệ thống nguyên khối (monolith), trình biên dịch và bộ test tích hợp trong cùng một repo trả lời câu hỏi này ngay lập tức. Nhưng trong microservices, consumer và provider nằm ở hai repo, hai pipeline, hai vòng đời khác nhau. Không có trình biên dịch nào bắc cầu qua ranh giới mạng. Đây chính là khoảng trống mà contract testing (kiểm thử hợp đồng) và cụ thể là Pact ra đời để lấp đầy.
2. Vì sao contract testing thắng E2E toàn phần cho microservices
Cách phòng thủ truyền thống là dựng một môi trường staging đầy đủ, triển khai tất cả các dịch vụ thật, rồi chạy một bộ test đầu-cuối (E2E) đi qua toàn bộ hệ thống. Cách này có vẻ chắc chắn nhưng đắt và giòn. Để test tương tác giữa ví và thanh toán, bạn phải khởi động cả hai dịch vụ, cùng cơ sở dữ liệu, hàng đợi tin nhắn và mọi phụ thuộc. Với hàng chục dịch vụ, một môi trường như thế mất nhiều giờ để dựng, dễ hỏng vì lý do không liên quan, và khi test đỏ bạn không biết dịch vụ nào có lỗi.
Contract testing đảo ngược bài toán. Thay vì chạy hai dịch vụ thật cùng lúc, ta tách phép kiểm thành hai nửa độc lập. Nửa consumer chạy consumer thật đối diện với một provider giả lập (mock provider), ghi lại chính xác các yêu cầu mà consumer gửi và phản hồi mà nó mong đợi. Nửa provider chạy provider thật và phát lại đúng những yêu cầu đó, kiểm rằng provider trả về phản hồi khớp. Không lúc nào ta cần cả hai dịch vụ cùng chạy. Mỗi nửa nhanh như unit test, chạy được trong pipeline riêng của từng đội, và khi đỏ nó chỉ đúng một điểm hỏng: hợp đồng bị phá.
- Phản hồi nhanh: mỗi nửa chạy trong vài giây, không cần dựng cả hệ thống.
- Định vị lỗi chính xác: test đỏ chỉ đúng một cặp consumer-provider.
- Độc lập vòng đời: mỗi đội chạy phần của mình trong pipeline riêng.
- Consumer-driven: hợp đồng phản ánh đúng những gì consumer thực sự dùng.
3. Pact hoạt động thế nào: hợp đồng do consumer sinh ra
Điểm khác biệt then chốt của Pact so với các cách tiếp cận khác nằm ở hai chữ 'consumer-driven' (do consumer dẫn dắt). Hợp đồng không được viết trước bởi kiến trúc sư rồi áp xuống. Thay vào đó, chính consumer, thông qua bài test của nó, tuyên bố: 'Tôi sẽ gửi yêu cầu này và tôi cần phản hồi có hình dạng này'. Khi bài test consumer chạy, thư viện Pact khởi động một mock provider trong bộ nhớ, consumer thật gọi vào mock đó, và Pact ghi lại mọi tương tác (interaction) thành một file JSON gọi là pact file.
Pact file này là hiện vật trung tâm của toàn bộ quy trình. Nó chứa danh sách các interaction, mỗi interaction gồm: một provider state (trạng thái tiên quyết mà provider cần có), một request (method, path, headers, body mong đợi) và một response (status, headers, body mà consumer sẽ nhận và xử lý được). Vì được sinh từ chính consumer thật đang chạy, hợp đồng luôn phản ánh đúng thực tế sử dụng, không bao giờ lệch pha với code. Sau đó pact file được chuyển sang phía provider để phát lại — đó là bước provider verification (kiểm chứng provider) mà bài tiếp theo sẽ đi sâu.
💬 Bình luận (0)
Chưa có bình luận nào. Hãy là người đầu tiên!