Mã của bạn phụ thuộc vào một dịch vụ mà bạn không kiểm soát. Một cổng thanh toán, một API của đối tác, một mainframe cũ kỹ, một microservice của đội khác. Sự phụ thuộc đó có thể đang bảo trì, giới hạn tần suất chạy thử nghiệm của bạn, tính phí mỗi lần gọi, hoặc trả về dữ liệu không ổn định. Vì vậy, quá trình phát triển của bạn bị đình trệ và quy trình CI (Tích hợp liên tục) của bạn chuyển sang màu đỏ vì những lý do không liên quan gì đến mã của bạn.
Ảo hóa dịch vụ (Service virtualization) khắc phục điều này. Bạn thay thế dịch vụ phụ thuộc thực bằng một dịch vụ giả lập hoạt động giống hệt, để bạn có thể xây dựng và thử nghiệm dựa trên thứ mà bạn hoàn toàn kiểm soát. Hướng dẫn này giải thích ảo hóa dịch vụ là gì, nó khác với mocking và stubbing như thế nào, tại sao các nhóm lại cần đến nó, và những công cụ nào thực hiện công việc này, với các lệnh thực tế cho WireMock, Hoverfly, Mountebank, các tùy chọn dành cho doanh nghiệp và máy chủ giả lập của Apidog cho ảo hóa hợp đồng API.
Ảo hóa dịch vụ là gì
Ảo hóa dịch vụ mô phỏng hành vi của một dịch vụ phụ thuộc mà bạn không kiểm soát để ứng dụng của bạn có thể giao tiếp với bản mô phỏng thay vì dịch vụ thực.
Từ "hành vi" ở đây rất quan trọng. Một dịch vụ ảo tốt không chỉ trả về một payload cố định. Nó khớp với các chi tiết yêu cầu, trả về các phản hồi khác nhau cho các đầu vào khác nhau, duy trì trạng thái qua các cuộc gọi, tiêm độ trễ và lỗi, và nói cùng một giao thức với hệ thống thực. Mục tiêu là một bản giả lập đủ thực tế để mã của bạn không thể nhận ra sự khác biệt trong quá trình phát triển và thử nghiệm.
Thuật ngữ "ảo hóa API" (API virtualization) có nghĩa tương tự khi dịch vụ phụ thuộc là một API HTTP, và "trình mô phỏng API" (API simulator) là công cụ tạo ra điểm cuối ảo. Coi cả hai thuật ngữ này là đồng nghĩa cho phần ảo hóa dịch vụ tập trung vào API.
Ảo hóa dịch vụ so với mocking so với stubbing
Ba thuật ngữ này có sự chồng chéo, và mọi người sử dụng chúng một cách lỏng lẻo. Dưới đây là cách thực tế để phân biệt chúng.
Một stub trả về một phản hồi được mã hóa cứng. Nó bỏ qua yêu cầu và luôn trả lời theo cùng một cách. Nếu thử nghiệm của bạn gọi GET /user/42 và stub được thiết lập để trả về một đối tượng người dùng cố định, nó sẽ trả về cùng đối tượng đó bất kể bạn gửi ID nào. Stub là dạng đơn giản nhất và nằm trong mã thử nghiệm của bạn.
Một mock là một stub nhưng cũng xác minh các tương tác. Nó ghi lại cách nó được gọi (phương thức nào, đối số nào, bao nhiêu lần) để thử nghiệm của bạn có thể xác nhận rằng mã của bạn đã sử dụng dịch vụ phụ thuộc một cách chính xác. Mock là một công cụ thử nghiệm đơn vị. Chúng thường chạy trong quy trình, cùng ngôn ngữ với mã của bạn. Bài giải thích API stubbing vs API mocking của chúng tôi đi sâu hơn vào sự phân chia này.
Ảo hóa dịch vụ hoạt động qua mạng, không phải trong mã của bạn. Nó thiết lập một dịch vụ đang chạy trên một cổng mạng thực sự và nói một giao thức thực sự. Ứng dụng của bạn kết nối với nó theo cùng cách nó sẽ kết nối với môi trường sản xuất, bằng cách thay đổi một URL cơ sở hoặc cài đặt proxy. Một dịch vụ ảo có thể có trạng thái, có thể khớp các điều kiện yêu cầu phức tạp, và có thể mô phỏng hành vi cấp độ giao thức như mã trạng thái HTTP, tiêu đề, truyền trực tuyến và lỗi kết nối.
Ranh giới giữa "mock" và "ảo hóa dịch vụ" khá mờ nhạt trong ngôn ngữ hàng ngày. Nhiều nhóm gọi một máy chủ mock HTTP đang chạy là "một mock" ngay cả khi nó thực hiện ảo hóa đầy đủ. Điều đó không sao cả. Sự khác biệt quan trọng là: bạn đang thay thế một đối tượng bên trong quy trình của bạn (mocking/stubbing) hay thiết lập một dịch vụ mạng mà ứng dụng của bạn kết nối đến (ảo hóa)? Xem Mocking API: mọi thứ bạn cần biết để biết về phía mock.
Tại sao các nhóm sử dụng ảo hóa dịch vụ
Ba vấn đề thúc đẩy hầu hết việc áp dụng.
Dịch vụ phụ thuộc chưa sẵn sàng. Các nhóm frontend và backend làm việc song song, và frontend cần một API hoạt động trước khi backend hoàn thành. Một dịch vụ ảo được xây dựng từ hợp đồng API cho phép frontend xây dựng dựa trên các phản hồi thực tế ngay hôm nay. Không ai phải chờ đợi.
Dịch vụ phụ thuộc đắt đỏ hoặc bị giới hạn tần suất. Các API của bên thứ ba thường tính phí mỗi lần gọi hoặc giới hạn tần suất mạnh mẽ, vì vậy việc chạy một bộ thử nghiệm đầy đủ chống lại một môi trường sandbox trực tiếp sẽ tiêu tốn hạn ngạch và tiền bạc. Một dịch vụ ảo có thể được truy cập miễn phí, do đó CI của bạn có thể chạy hàng nghìn kịch bản mỗi ngày.
Dịch vụ phụ thuộc không ổn định hoặc khó kiểm soát. Bạn không thể dễ dàng yêu cầu một nhà cung cấp thanh toán thực trả về một lỗi cụ thể, hết thời gian chờ hoặc gửi một phản hồi bị lỗi. Một dịch vụ ảo tạo ra bất kỳ điều nào trong số đó theo yêu cầu, đây là cách bạn kiểm tra các đường dẫn lỗi gây sự cố trong môi trường sản xuất.
Ngoài ra còn có lợi ích kiểm soát. Các dịch vụ phụ thuộc thực có dữ liệu thay đổi dưới sự giám sát của bạn. Một dịch vụ ảo cung cấp cho bạn một trạng thái cố định, đã biết, vì vậy các thử nghiệm của bạn là xác định. Một bản dựng xanh có nghĩa là mã của bạn hoạt động, chứ không phải do một hệ thống từ xa tình cờ hoạt động tốt vào ngày hôm đó.
Ảo hóa dịch vụ hoạt động như thế nào
Hầu hết các công cụ tuân theo một trong hai mô hình.
Ghi và phát lại. Bạn chỉ công cụ vào dịch vụ thực như một proxy, chạy lưu lượng truy cập thực qua nó một lần, và nó sẽ ghi lại các cặp yêu cầu/phản hồi. Sau đó, nó sẽ phát lại các bản ghi đó thay vì gọi dịch vụ thực. Điều này nhanh chóng vì bạn không cần viết thủ công các phản hồi. Chế độ chụp của Hoverfly và chế độ ghi của WireMock đều hoạt động theo cách này.
Dựa trên đặc tả. Bạn định nghĩa dịch vụ ảo từ một hợp đồng API hoặc bằng tay: khai báo yêu cầu nào khớp với phản hồi nào, thêm các giá trị động và thiết lập hành vi có trạng thái. Điều này đòi hỏi nhiều công sức ban đầu hơn nhưng mang lại cho bạn toàn quyền kiểm soát và hoạt động ngay cả khi dịch vụ thực chưa tồn tại.
Dù bằng cách nào, cơ chế cốt lõi là khớp yêu cầu. Khi một yêu cầu đến, công cụ kiểm tra nó với một tập hợp các quy tắc (đường dẫn, phương thức, tham số truy vấn, tiêu đề, nội dung) và trả về phản hồi liên quan đến lần khớp đầu tiên. Các công cụ tốt hơn bổ sung thêm tính năng tạo mẫu (templating), để một phản hồi có thể lặp lại các phần của yêu cầu hoặc tạo ra các giá trị mới trong mỗi lần gọi. Đó là cách một dịch vụ ảo cảm thấy sống động thay vì tĩnh.
Các công cụ
Dưới đây là các tùy chọn chính, từ các công cụ mã nguồn mở tập trung vào HTTP đến các bộ công cụ doanh nghiệp lớn. Hãy xác minh các phiên bản và tính năng hiện tại dựa trên tài liệu riêng của từng dự án trước khi cam kết, vì chúng thay đổi nhanh chóng.
WireMock
WireMock là công cụ mocking API và ảo hóa dịch vụ mã nguồn mở dành cho HTTP. Nó chạy như một tiến trình độc lập, được nhúng trong các thử nghiệm JVM hoặc trong một container. Bạn định nghĩa các ánh xạ stub dưới dạng JSON, và WireMock khớp các yêu cầu đến với chúng. Nó hỗ trợ ghi và phát lại (record-and-playback) chống lại một dịch vụ thực, tạo mẫu phản hồi, các kịch bản có trạng thái, tiêm lỗi và mô phỏng độ trễ.
Chạy file JAR độc lập:
java -jar wiremock-standalone-3.13.2.jar --port 8080
Sau đó đăng ký một stub qua API quản trị của nó:
curl -X POST http://localhost:8080/__admin/mappings \
-H "Content-Type: application/json" \
-d '{
"request": {
"method": "GET",
"url": "/user/42"
},
"response": {
"status": 200,
"headers": { "Content-Type": "application/json" },
"jsonBody": { "id": 42, "name": "Ada Lovelace" }
}
}'
WireMock mạnh nhất khi các dịch vụ phụ thuộc của bạn là HTTP và bạn muốn kiểm soát chi tiết trong các thiết lập JVM hoặc container. Có một phiên bản thương mại được lưu trữ (WireMock Cloud) nếu bạn muốn một tùy chọn được quản lý.
Hoverfly
Hoverfly là một công cụ ảo hóa dịch vụ mã nguồn mở nhẹ được viết bằng Go. Nó hoạt động như một proxy: bạn định tuyến lưu lượng truy cập đi của ứng dụng qua Hoverfly, và nó sẽ ghi lại các phản hồi thực hoặc phát lại các phản hồi được mô phỏng. Nó chạy ở nhiều chế độ, bao gồm chụp, mô phỏng, do thám, sửa đổi, tổng hợp và so sánh.
Một luồng cơ bản ghi-sau-đó-phát lại sử dụng lệnh hoverctl của nó:
hoverctl start
hoverctl mode capture
curl --proxy http://localhost:8500 https://api.example.com/user/42
hoverctl export simulation.json
hoverctl mode simulate
API quản trị của Hoverfly chạy trên cổng 8888 và proxy của nó trên cổng 8500 theo mặc định. Mô hình proxy ghi và phát lại làm cho nó trở nên phù hợp khi bạn muốn ảo hóa lưu lượng truy cập hiện có một cách nhanh chóng thay vì viết thủ công từng phản hồi. Cũng có Hoverfly Cloud được lưu trữ.
Mountebank
Mountebank là một công cụ mã nguồn mở được xây dựng xoay quanh các "imposters" (đối tượng giả lập). Một imposter là một dịch vụ ảo duy nhất lắng nghe trên một cổng và nói một giao thức. Ngoài HTTP và HTTPS, Mountebank cũng xử lý TCP và SMTP, điều này hữu ích khi dịch vụ phụ thuộc của bạn không phải là một API REST đơn thuần. Bạn kiểm soát nó hoàn toàn qua một mặt phẳng điều khiển REST trên cổng 2525.
Cài đặt và khởi động nó:
npm install -g mountebank
mb
Sau đó tạo một imposter bằng cách POST JSON tới cổng điều khiển:
curl -X POST http://localhost:2525/imposters \
-H "Content-Type: application/json" \
-d '{
"port": 4545,
"protocol": "http",
"stubs": [{
"predicates": [{ "equals": { "method": "GET", "path": "/user/42" } }],
"responses": [{ "is": { "statusCode": 200, "body": "{\"id\":42,\"name\":\"Ada Lovelace\"}" } }]
}]
}'
Các vị từ (predicates) quyết định yêu cầu nào khớp; các phản hồi định nghĩa những gì được trả về. Phạm vi đa giao thức của Mountebank là yếu tố tạo nên sự khác biệt của nó trong số các tùy chọn mã nguồn mở.
Parasoft Virtualize và Broadcom Service Virtualization
Đối với các doanh nghiệp lớn, những cái tên nổi bật là Parasoft Virtualize và Broadcom Service Virtualization (một phần của bộ DevTest). Đây là các nền tảng thương mại được xây dựng cho các nhóm cần mô phỏng nhiều giao thức và định dạng thông điệp ngoài HTTP, kết nối các dịch vụ ảo với các nguồn dữ liệu thực và quản lý ảo hóa trên nhiều môi trường một cách tập trung.
Parasoft Virtualize quảng cáo hỗ trợ một loạt các giao thức và định dạng thông điệp tiêu chuẩn ngành, cộng với các dịch vụ ảo dựa trên dữ liệu được cung cấp từ các nguồn dữ liệu bên ngoài. Giải pháp của Broadcom nhắm mục tiêu các kịch bản doanh nghiệp tương tự với các môi trường ảo hóa cho phát triển và thử nghiệm song song. Cả hai đều đi kèm với giấy phép và chi phí thiết lập dành cho doanh nghiệp, vì vậy chúng phù hợp khi bạn ảo hóa các giao thức cũ (mainframe, MQ, tin nhắn có độ dài cố định) ở quy mô lớn thay vì một vài điểm cuối REST. Nếu các dịch vụ phụ thuộc của bạn là API HTTP thông thường, các công cụ mã nguồn mở ở trên hoặc một nền tảng API sẽ đáp ứng nhu cầu của bạn với chi phí thấp hơn nhiều. Hãy xác nhận danh sách giao thức và giá cả hiện tại với từng nhà cung cấp.
Apidog: ảo hóa dịch vụ nhẹ cho hợp đồng API
Nếu dịch vụ phụ thuộc bạn cần ảo hóa là một API HTTP được định nghĩa bằng một hợp đồng, Apidog sẽ xử lý phần đó mà không cần công cụ riêng. Khi bạn thiết kế hoặc nhập một API, Apidog sẽ tự động thiết lập một máy chủ giả lập (mock server) cho nó. Frontend hoặc bộ thử nghiệm của bạn sẽ có một điểm cuối hoạt động chỉ từ schema, trước khi bất kỳ mã backend nào tồn tại.
Điều làm cho điều này gần với ảo hóa hơn một stub đơn thuần là mock của Apidog nhận biết được schema và có tính động. Smart Mock của nó đọc JSON Schema của bạn và tạo ra các phản hồi tuân thủ mọi ràng buộc (kiểu dữ liệu, định dạng, giá trị liệt kê, trường bắt buộc). Nó cũng khớp tên trường với các quy tắc tích hợp, vì vậy một trường tên email sẽ trả về một email hợp lệ và một trường tên city sẽ trả về tên thành phố, thay vì dữ liệu giữ chỗ vô nghĩa. Để kiểm soát chi tiết hơn, các giá trị động dựa trên Faker.js tạo ra dữ liệu thực tế trong mỗi lần gọi, và bạn có thể ghim các giá trị cố định nơi bạn cần tính xác định.
Bạn cũng có thể viết các quy tắc mock tùy chỉnh trả về các phản hồi khác nhau dựa trên các tham số yêu cầu, đây là hành vi khớp yêu cầu phân biệt ảo hóa với một stub cố định. Máy chủ mock chạy trong GUI hoặc headless, vì vậy nó phù hợp cho cả phát triển cục bộ và CI. Xem cách mock một API trong một phút để xem hướng dẫn chi tiết.
Apidog phù hợp ở đâu: ảo hóa dịch vụ hợp đồng API cho HTTP/REST và các giao thức tương tự. Apidog không phù hợp ở đâu: nó không phải là một công cụ ghi và phát lại dựa trên proxy cho các giao thức cũ tùy ý, và nó không phải là một trình tạo tải. Đối với những trường hợp đó, các công cụ trên là lựa chọn đúng. Để so sánh công cụ chi tiết hơn, xem so sánh các công cụ mocking API trực tuyến và 10 công cụ mocking API tốt nhất.
Cách bắt đầu
Chọn công cụ phù hợp với dịch vụ phụ thuộc, không phải ngược lại.
- Xác định giao thức. HTTP/REST đơn thuần? Hầu hết mọi thứ ở đây đều hoạt động, và một nền tảng API là ít rắc rối nhất. Cần TCP hoặc SMTP? Mountebank. Các giao thức doanh nghiệp cũ ở quy mô lớn? Parasoft hoặc Broadcom.
- Quyết định ghi hay chỉ định. Nếu dịch vụ thực đã tồn tại và bạn có thể định tuyến lưu lượng truy cập qua nó, ghi-và-phát lại (Hoverfly, chế độ ghi của WireMock) là nhanh nhất. Nếu nó chưa tồn tại, hãy điều khiển dịch vụ ảo từ hợp đồng API (Apidog, stubs của WireMock, imposters của Mountebank).
- Chỉ ứng dụng của bạn vào nó. Thay đổi một URL cơ sở hoặc cài đặt proxy trong môi trường thử nghiệm hoặc phát triển của bạn, và giữ nó trong cấu hình để việc chuyển đổi giữa ảo và thực là một biến số duy nhất.
- Thêm các trường hợp lỗi. Lợi ích là thử nghiệm các đường dẫn lỗi, vì vậy hãy thêm các phản hồi cho 4xx và 5xx, hết thời gian chờ và các nội dung bị lỗi. Đó là phần mà các dịch vụ phụ thuộc thực sẽ không cho phép bạn diễn tập.
- Chạy nó trong CI. Một dịch vụ ảo chỉ chạy trên laptop của bạn giúp bạn nhưng không giúp quá trình xây dựng. Đặt nó vào quy trình để "xanh" có nghĩa là "xanh". Nếu bạn cần một mock chạy không giao diện người dùng (headless) trong CI mà không cần GUI, hãy xem các công cụ mock API headless.
Để hiểu sự đánh đổi giữa các dịch vụ phụ thuộc ảo và trực tiếp nói chung, bài viết máy chủ mock vs máy chủ thực sẽ trình bày khi nào nên sử dụng từng loại.
Câu hỏi thường gặp
Ảo hóa dịch vụ có giống với mocking không? Không hoàn toàn. Mocking thường thay thế một đối tượng bên trong quy trình của bạn cho các thử nghiệm đơn vị và xác minh cách nó được gọi. Ảo hóa dịch vụ thiết lập một dịch vụ đang chạy trên một cổng mạng mà ứng dụng của bạn kết nối qua một giao thức thực. Mọi người gọi các dịch vụ ảo HTTP là "mocks" trong cách sử dụng thông thường, điều đó không sao, nhưng sự khác biệt kỹ thuật là đối tượng trong quy trình so với dịch vụ mạng.
Khi nào tôi nên sử dụng một dịch vụ ảo thay vì một môi trường thử nghiệm thực? Sử dụng ảo hóa khi dịch vụ phụ thuộc thực không khả dụng, bị giới hạn tần suất, đắt đỏ hoặc khó kiểm soát. Nó cung cấp cho bạn các phản hồi xác định, miễn phí, luôn có sẵn và cho phép bạn mô phỏng các lỗi mà bạn không thể kích hoạt trên hệ thống thực. Giữ một tập hợp thử nghiệm nhỏ hơn đối với dịch vụ thực để có sự tự tin tích hợp cuối cùng.
Một dịch vụ ảo có thể có trạng thái không? Có. Các công cụ như WireMock và Mountebank hỗ trợ các kịch bản có trạng thái trong đó phản hồi thay đổi dựa trên các yêu cầu trước đó, ví dụ: trả về một giỏ hàng trống, sau đó là một giỏ hàng có các mặt hàng sau một yêu cầu POST. Điều này quan trọng khi bạn mô phỏng các quy trình làm việc thay vì các cuộc gọi đơn lẻ.
Tôi có cần một công cụ doanh nghiệp trả phí không? Chỉ khi bạn ảo hóa nhiều giao thức không phải HTTP hoặc quản lý ảo hóa trong một tổ chức lớn. Đối với các API HTTP thông thường, các công cụ mã nguồn mở (WireMock, Hoverfly, Mountebank) hoặc một máy chủ mock nền tảng API sẽ xử lý công việc mà không cần giấy phép doanh nghiệp.
Cách nhanh nhất để ảo hóa một API HTTP từ đặc tả của nó là gì? Nhập định nghĩa API vào một công cụ tạo mock từ schema. Apidog tự động làm điều này: nhập đặc tả OpenAPI của bạn và bạn sẽ có một máy chủ mock nhận biết schema với dữ liệu động thực tế, không cần viết phản hồi. Từ đó, bạn thêm các quy tắc tùy chỉnh cho các điều kiện yêu cầu/phản hồi cụ thể mà các thử nghiệm của bạn cần.
