Contract testing trong Microservice với Swagger, Prism & Dredd

Dzũng Nguyễn
Edumall Engineering
7 min readMar 19, 2019

Trong kiến trúc SoA/ microservices, contract là một thoả thuận giữa service provider và các consumers của service provider đó. Từ contract này, consumer biết cách sử dụng service đó, data nào có thể gửi được và các responses có thể nhận. Contract đảm bảo tính integrity cần thiết cho hệ thống yêu cầu sự ổn định.

Vấn đề phát sinh chủ yếu khi hệ thống được bồi đắp thêm, các service cũng phải thay đổi, API specification cũng vì đó mà thay đổi theo. Để hệ thống chạy tốt, rõ ràng cần phải thực hiện sự thay đổi trên service đó và consumer cần hoàn thành theo thoả thuận với service. Nhưng làm thế nào để có thể chắc chắn được việc này? Câu trả lời chính là Contract testing.

Hình 1: Ví dụ về contract trong microservice/ SoA

Contract testing là gì?

Contract testing là kĩ thuật viết test đảm bảo chắc chắn phía service provider và consumer hoàn thành theo đúng những gì đã thoả thuận trong contract.

Từ định nghĩa này, có 2 cách tiếp cận:

  1. Phía service provider: Cần validate phần backend đảm nhiệm những gì được nêu trong contract. Cụ thể, nếu là REST service, chúng ta cần check các điểm sau:
  • Service liệu có đang handle data request và response đúng không(dữ liệu XML, JSON, các tham số truy vấn…)?
  • Format của data có đủ (độ dài tối đa, loại data, các trường bắt buộc …)?
  • HTTP codes có được sử dụng đúng không?
  • Liệu backend trả lời adapt tuỳ theo từng request trên từng resource/ method khác nhau?

2. Phía consumer: Kiểm tra xem liệu consumer có khả năng phù hợp với backend service như contract mô tả hay không. Tức là chúng ta nên focus vào việc kiểm tra xem consumer có sẵn sàng handle được các loại data input và output khác nhau, các response code… Cũng nên lưu ý rằng một consumer thực ra chỉ là một cấu phần của phần mềm để inject vào các component (như 1 web app, 1 mobile app, 1 batch process, một microservice). Nếu nó không thể tương tác chuẩn với backend service thì component phía trên nó sẽ fail trong quá trình runtime và sẽ có vấn đề lớn xảy ra không sớm thì muộn.

Tại sao lại cần contract testing?

Lý do đây là một trong những cách để giúp chúng ta hạn chế việc hệ thống fail vì những nguyên nhân không đoán định được khi hệ thống đó ngày càng trở nên phức tạp hơn.

Trong các hệ thống base trên microservice, các team service provider & service consumer thường không là một. Contract testing giúp cân bằng quá trình tích hợp giữa service provider & consumer và tham dự sâu vào quá trình này khi sự thay đổi nào đó trên service contract bắt đầu có ảnh hưởng tới consumer của nó, sự thay đổi này có thể:

  • Thuộc loại thay đổi hoàn toàn: service client được host trên ứng dụng consumer sẽ không hoạt động đúng.
  • Thuộc loại tương thích ngược: consumer vẫn làm việc tốt với service contract mới. Điều này hoàn toàn phụ thuộc vào service consumer có thay đổi service client của nó hay không.

Thường thì contract chính là API specification. Contract này được bồi đắp dần dần bằng cách thêm các scenario khác nhau cho các consumer khác nhau.

Contract testing từ góc nhìn của service provider

Như đã đề cập ở trên, chúng ta cần xác minh lại xem service phía backend đã thực thi theo API specification hay chưa. Điều này rất quan trọng vì việc thay đổi hoàn toàn là có khả năng xảy ra và quá trình tích hợp giữa service và consumer sẽ bị fail.

Service contract trong kiến trúc SoA thường là API specification. Serivce contract có thể được bổ sung thêm các scenario cụ thể (given/ when/ then) bằng cách sử dụng các tool khác nhau như Spring Cloud Contract hoặc Pact.

Các engineer thường sử dụng format chuẩn để document các API như Swagger, API Blueprint hay RAML.

Hãy thử check example project ở đây. Trong project này có file ApiV1.yaml được viết sử dụng Swagger — Open API Specification (https://swagger.io/specification/).

Service trong example project này cung cấp các tính năng sau:

  • Tìm kiếm các đơn hàng
  • Tìm kiếm một đơn hàng cụ thể
  • Tạo đơn hàng mới

Tất cả đều được viết dùng REST. Data được trao đổi giữa service và consumer là một trong các loại sau:

  • Đơn hàng đã có
  • Đơn hàng mới
  • Tập các đơn hàng

Với mỗi loại data, sẽ đi kèm các property của data đó: format, mandatory hay optional…

Giờ hãy giả thiết rằng chúng ta đã implement backend service theo API specification (project URL: https://github.com/marlandy/contracttesting/) và đang chạy trên server.

Giờ chúng ta sử dụng Dredd để lấy các chuẩn đoán hiệu quả hơn. Tool này giúp chúng ta verify được backend service có thể đảm bảo chạy được example theo những gì đã đặc tả trên API document và trên hết, chúng ta không phải làm gì cả.

Việc sử dụng Dredd khá đơn giản, chỉ cần dùng npm cài Dredd lên máy desktop hoặc CI server … bằng lệnh:

> npm install -g dredd

Sau khi cài service chạy trên localhost ở port 8080, và API document để ở link http://localhost:8080/awesomesite/ApiV1.yaml, chúng ta chạy command dưới đây:

> dredd http://localhost:8080/awesomesite/ApiV1.yaml http://localhost:8080

Kết quả như screenshot:

Demo chạy dredd cho một endpoint

Giải thích cơ chế hoạt động của Dredd như sau: Dredd gửi một vài HTTP request tới backend service (host ở localhost:8080). Request này được tạo ra bằng các dữ liệu mẫu được định nghĩa ở API document sử dụng property x-example. Dredd trông chờ nhận được response 2xx từ service như mô tả ở API specification. Các scenario cần được xác minh ở Dredd đều theo một luồng: đưa vào 1 endpoint, sau khi một request example được gửi đi, thì service trả về 1 response 2xx và headers/ body tương ứng. Thuật ngữ này được Dredd gọi là automatic expectations.

Chuyện gì sẽ xảy ra nếu ta thay đổi property order status ở backend service mà không được mô tả trên API document?

Lấy ví dụ ta thay đổi format của order status từ integer sang kiểu string, rồi chạy lại dredd.

Dưới đây là kết quả:

Thay đổi format order status và chạy lại dredd

Như hình trên cho thấy, Dredd show ra sự khác biệt giữa service và specification của nó. Sự thay đổi ở service rõ ràng có impact không tốt tới consumer do đã phá vỡ tính tích hợp ở đây.

Hiển nhiên Dredd cho phép engineer viết các test script cho nhiều scenario khác và điều đó đòi hỏi các engineer phải tự chủ nhiều hơn ngoài các default scenario.

Contract testing từ góc nhìn của service consumer

Ở góc nhìn này, chúng ta phải xác minh liệu service consumer có tương tác được với backend service qua API của service đó hay không. Cụ thể là cần kiểm tra liệu consumer gửi request và handle response có đúng hay không?

Tương tự như trên, chúng ta cũng bắt đầu từ API specification và tiến hành sử dụng Prism (https://stoplight.io/platform/prism/).

Đây là công cụ giúp các engineer tạo và chạy một implementation giả cho service API .

Giả sử như ta đã cài xong Prism và đường dẫn tương đối tới API specification file là src/main/webapp/ApiV1.yaml và tiến hành chạy command sau:

> prism run — mock — list — spec src/main/webapp/ApiV1.yaml

Command trên giúp chúng ta tạo ra một service fake chạy ở URL http://localhost:4010 và sẵn sàng handle các request, xác minh, rồi trả về các response theo API specification. Prism sẽ trả về các response này.

Rõ ràng chúng ta thấy lợi ích lớn từ việc này: có thể xác minh được consumer hoàn toàn tương tác đúng với service hay không mà không cần động tới một dòng code nào ở backend service. Điều này giúp cho service consumer team làm phần việc của họ chỉ với API specification, tăng tốc độ implementation, độ chính xác và thời gian deployment.

Tổng kết

Có nhiều tool đảm nhiệm nhiệm vụ tạo ra các scenario giữa consumer & service như Pact hay Spring Cloud Contract. Tuy nhiên, sẽ có một vài vấn đề phát sinh do sự overlap giữa các scenario này với API document của service.

Đây chính là lý do các tool như Dredd & Prism trở nên hữu dụng hơn hết khi cho chúng ta cảm nhận được các phân tích bước đầu của quá trình tích hợp giữa service provider & consumer theo cách đơn giản nhất là sử dụng API specification.

Nhưng các bạn cũng nên lưu ý rằng kĩ thuật này không phải là sự thay thế hoàn toàn cho unit testing mà chỉ là sự bổ sung có giá trị.

Happy testing!

— — — — — — — — -

Tham khảo:

  1. https://blog.usejournal.com/a-story-of-contract-testing-fe565b7e9f51
  2. https://martinfowler.com/bliki/ContractTest.html

--

--