Trong các dự án liên quan đến phát triển kiến trúc microservice, CI/CD chuyển từ loại cơ hội dễ chịu sang loại cần thiết cấp bách. Kiểm thử tự động là một phần không thể thiếu của quá trình tích hợp liên tục, một cách tiếp cận hiệu quả có thể mang lại cho nhóm nhiều buổi tối vui vẻ bên gia đình và bạn bè. Nếu không, dự án có nguy cơ không bao giờ được hoàn thành.
Có thể bao quát toàn bộ mã microservice bằng các bài kiểm tra đơn vị với các đối tượng mô phỏng, nhưng điều này chỉ giải quyết được một phần vấn đề và để lại nhiều câu hỏi và khó khăn, đặc biệt là khi kiểm thử làm việc với dữ liệu. Như mọi khi, vấn đề cấp bách nhất là kiểm tra tính nhất quán của dữ liệu trong cơ sở dữ liệu quan hệ, kiểm tra hoạt động với các dịch vụ đám mây và đưa ra các giả định không chính xác khi viết các đối tượng mô phỏng.
Tất cả những điều này và một số vấn đề khác có thể được giải quyết bằng cách kiểm tra toàn bộ vi dịch vụ trong vùng chứa Docker. Một lợi thế chắc chắn để đảm bảo tính hợp lệ của các thử nghiệm là các hình ảnh Docker giống nhau khi đưa vào sản xuất đều được thử nghiệm.
Tự động hóa phương pháp này có một số vấn đề, giải pháp sẽ được mô tả dưới đây:
- xung đột các tác vụ song song trong cùng một máy chủ docker;
- xung đột định danh trong cơ sở dữ liệu trong quá trình lặp lại thử nghiệm;
- chờ đợi microservice sẵn sàng;
- hợp nhất và xuất nhật ký ra hệ thống bên ngoài;
- kiểm tra các yêu cầu HTTP gửi đi;
- kiểm tra ổ cắm web (sử dụng SignalR);
- kiểm tra xác thực và ủy quyền OAuth.
Bài viết này dựa trên tại SECR 2019. Vì vậy, đối với những người quá lười đọc, .

Trong bài viết này, tôi sẽ hướng dẫn bạn cách sử dụng tập lệnh để chạy dịch vụ đang được thử nghiệm, cơ sở dữ liệu và các dịch vụ Amazon AWS trong Docker, sau đó kiểm tra trên Postman và sau khi hoàn tất, hãy dừng và xóa các vùng chứa đã tạo. Các thử nghiệm được thực hiện mỗi khi mã thay đổi. Bằng cách này, chúng tôi đảm bảo rằng mỗi phiên bản đều hoạt động chính xác với cơ sở dữ liệu và dịch vụ AWS.
Tập lệnh tương tự được chạy bởi chính các nhà phát triển trên máy tính để bàn Windows của họ và bởi máy chủ Gitlab CI trong Linux.
Để hợp lý, việc giới thiệu các thử nghiệm mới không yêu cầu cài đặt các công cụ bổ sung trên máy tính của nhà phát triển hoặc trên máy chủ nơi các thử nghiệm được chạy trên một cam kết sẽ giải quyết được vấn đề này.
Quá trình kiểm tra phải chạy trên máy chủ cục bộ vì những lý do sau:
- Mạng không bao giờ hoàn toàn đáng tin cậy. Trong hàng nghìn yêu cầu, một yêu cầu có thể thất bại;
Trong trường hợp này, quá trình kiểm tra tự động sẽ không hoạt động, công việc sẽ dừng lại và bạn sẽ phải tìm lý do trong nhật ký; - Một số dịch vụ bên thứ ba không cho phép yêu cầu quá thường xuyên.
Ngoài ra, việc sử dụng giá đỡ là điều không mong muốn vì:
- Một giá đỡ có thể bị hỏng không chỉ bởi mã xấu chạy trên đó mà còn bởi dữ liệu mà mã chính xác không thể xử lý;
- Cho dù chúng tôi có cố gắng hoàn nguyên tất cả các thay đổi do quá trình kiểm thử thực hiện trong quá trình kiểm thử đến mức nào thì vẫn có thể xảy ra sai sót (nếu không thì tại sao lại kiểm thử?).
Về tổ chức dự án và quy trình
Công ty chúng tôi đã phát triển một ứng dụng web vi dịch vụ chạy trong Docker trên đám mây Amazon AWS. Các bài kiểm tra đơn vị đã được sử dụng trong dự án nhưng thường xảy ra lỗi mà các bài kiểm tra đơn vị không phát hiện được. Cần phải kiểm tra toàn bộ microservice cùng với cơ sở dữ liệu và các dịch vụ của Amazon.
Dự án sử dụng quy trình tích hợp liên tục tiêu chuẩn, bao gồm thử nghiệm vi dịch vụ với mọi cam kết. Sau khi giao nhiệm vụ, nhà phát triển sẽ thực hiện các thay đổi đối với vi dịch vụ, kiểm tra thủ công và chạy tất cả các thử nghiệm tự động có sẵn. Nếu cần thiết, nhà phát triển sẽ thay đổi các bài kiểm tra. Nếu không tìm thấy vấn đề gì, một cam kết sẽ được thực hiện cho nhánh của vấn đề này. Sau mỗi lần xác nhận, các bài kiểm tra sẽ tự động được chạy trên máy chủ. Việc sáp nhập vào một nhánh chung và khởi chạy các thử nghiệm tự động trên nhánh đó xảy ra sau khi xem xét thành công. Nếu các thử nghiệm trên nhánh dùng chung vượt qua, dịch vụ sẽ tự động được cập nhật trong môi trường thử nghiệm trên Amazon Elastic Container Service (dự phòng). Giá đỡ là cần thiết cho tất cả các nhà phát triển và người thử nghiệm và không nên phá vỡ nó. Người kiểm tra trong môi trường này kiểm tra bản sửa lỗi hoặc tính năng mới bằng cách thực hiện kiểm tra thủ công.
Kiến trúc dự án

Ứng dụng này bao gồm hơn mười dịch vụ. Một số trong số chúng được viết bằng .NET Core và một số bằng NodeJ. Mỗi dịch vụ chạy trong một bộ chứa Docker trong Dịch vụ bộ chứa đàn hồi của Amazon. Mỗi cơ sở dữ liệu đều có cơ sở dữ liệu Postgres riêng và một số còn có Redis. Không có cơ sở dữ liệu chung. Nếu một số dịch vụ cần cùng một dữ liệu thì dữ liệu này khi thay đổi sẽ được truyền đến từng dịch vụ này thông qua SNS (Dịch vụ thông báo đơn giản) và SQS (Dịch vụ hàng đợi đơn giản của Amazon) và các dịch vụ sẽ lưu dữ liệu đó vào cơ sở dữ liệu riêng của chúng.
SQS và SNS
SQS cho phép bạn đặt tin nhắn vào hàng đợi và đọc tin nhắn từ hàng đợi bằng giao thức HTTPS.
Nếu một số dịch vụ đọc một hàng đợi thì mỗi tin nhắn chỉ đến một trong số chúng. Điều này hữu ích khi chạy nhiều phiên bản của cùng một dịch vụ để phân phối tải giữa chúng.
Nếu bạn muốn mỗi tin nhắn được gửi đến nhiều dịch vụ thì mỗi người nhận phải có hàng đợi riêng và cần có SNS để sao chép tin nhắn thành nhiều hàng đợi.
Trong SNS, bạn tạo một chủ đề và đăng ký chủ đề đó, chẳng hạn như hàng đợi SQS. Bạn có thể gửi tin nhắn đến chủ đề. Trong trường hợp này, tin nhắn sẽ được gửi đến từng hàng đợi đã đăng ký chủ đề này. SNS không có phương pháp đọc tin nhắn. Nếu trong quá trình gỡ lỗi hoặc kiểm tra, bạn cần tìm hiểu những gì được gửi tới SNS, bạn có thể tạo hàng đợi SQS, đăng ký chủ đề mong muốn và đọc hàng đợi.

Cổng API
Hầu hết các dịch vụ không thể truy cập trực tiếp từ Internet. Quyền truy cập được thực hiện thông qua API Gateway để kiểm tra quyền truy cập. Đây cũng là dịch vụ của chúng tôi và cũng có những thử nghiệm cho dịch vụ này.
Thông báo thời gian thực
Ứng dụng sử dụng để hiển thị thông báo theo thời gian thực cho người dùng. Điều này được thực hiện trong dịch vụ thông báo. Nó có thể truy cập trực tiếp từ Internet và bản thân nó hoạt động với OAuth, vì việc xây dựng hỗ trợ cho các ổ cắm Web vào Gateway là không thực tế so với việc tích hợp OAuth và dịch vụ thông báo.
Phương pháp thử nghiệm nổi tiếng
Kiểm tra đơn vị thay thế những thứ như cơ sở dữ liệu bằng các đối tượng giả. Ví dụ: nếu một vi dịch vụ cố gắng tạo một bản ghi trong bảng có khóa ngoại và bản ghi được tham chiếu bởi khóa đó không tồn tại thì yêu cầu không thể được thực thi. Kiểm tra đơn vị không thể phát hiện điều này.
В Đề xuất sử dụng cơ sở dữ liệu trong bộ nhớ và triển khai các đối tượng giả.
Cơ sở dữ liệu trong bộ nhớ là một trong những DBMS được Entity Framework hỗ trợ. Nó được tạo ra đặc biệt để thử nghiệm. Dữ liệu trong cơ sở dữ liệu như vậy chỉ được lưu trữ cho đến khi quá trình sử dụng nó kết thúc. Nó không yêu cầu tạo bảng và không kiểm tra tính toàn vẹn của dữ liệu.
Các đối tượng mô phỏng chỉ lập mô hình lớp mà chúng đang thay thế trong phạm vi mà nhà phát triển thử nghiệm hiểu được cách thức hoạt động của nó.
Cách để Postgres tự động bắt đầu và thực hiện di chuyển khi bạn chạy thử nghiệm không được chỉ định trong bài viết của Microsoft. Giải pháp của tôi thực hiện điều này và ngoài ra, không thêm bất kỳ mã nào cụ thể để kiểm tra vào chính vi dịch vụ.
Hãy chuyển sang giải pháp
Trong quá trình phát triển, rõ ràng là các bài kiểm tra đơn vị là không đủ để tìm ra tất cả các vấn đề một cách kịp thời, vì vậy người ta quyết định tiếp cận vấn đề này từ một góc độ khác.
Thiết lập môi trường thử nghiệm
Nhiệm vụ đầu tiên là triển khai một môi trường thử nghiệm. Các bước cần thiết để chạy một microservice:
- Định cấu hình dịch vụ đang được thử nghiệm cho môi trường cục bộ, chỉ định chi tiết để kết nối với cơ sở dữ liệu và AWS trong các biến môi trường;
- Bắt đầu Postgres và thực hiện di chuyển bằng cách chạy Liquibase.
Trong các DBMS quan hệ, trước khi ghi dữ liệu vào cơ sở dữ liệu, bạn cần tạo một lược đồ dữ liệu, hay nói cách khác là các bảng. Khi cập nhật một ứng dụng, các bảng phải được đưa về dạng mà phiên bản mới sử dụng và tốt nhất là không làm mất dữ liệu. Điều này được gọi là di cư. Tạo bảng trong cơ sở dữ liệu trống ban đầu là trường hợp di chuyển đặc biệt. Việc di chuyển có thể được tích hợp vào chính ứng dụng. Cả .NET và NodeJS đều có khung di chuyển. Trong trường hợp của chúng tôi, vì lý do bảo mật, các dịch vụ vi mô bị tước quyền thay đổi lược đồ dữ liệu và việc di chuyển được thực hiện bằng Liquibase. - Khởi chạy Amazon LocalStack. Đây là cách triển khai các dịch vụ AWS để chạy tại nhà. Có sẵn image cho LocalStack trên Docker Hub.
- Chạy tập lệnh để tạo các thực thể cần thiết trong LocalStack. Tập lệnh Shell sử dụng AWS CLI.
Dùng để test dự án . Nó đã tồn tại trước đó, nhưng nó đã được khởi chạy thủ công và thử nghiệm một ứng dụng đã được triển khai tại quầy. Công cụ này cho phép bạn thực hiện các yêu cầu HTTP(S) tùy ý và kiểm tra xem phản hồi có như mong đợi hay không. Các truy vấn được kết hợp thành một bộ sưu tập và toàn bộ bộ sưu tập có thể được chạy.

Kiểm tra tự động hoạt động như thế nào?
Trong quá trình thử nghiệm, mọi thứ đều hoạt động trong Docker: dịch vụ được thử nghiệm, Postgres, công cụ di chuyển và Postman, hay đúng hơn là phiên bản bảng điều khiển của nó - Newman.
Docker giải quyết một số vấn đề:
- Độc lập với cấu hình máy chủ;
- Cài đặt các phần phụ thuộc: Docker tải hình ảnh từ Docker Hub;
- Đưa hệ thống về trạng thái ban đầu: chỉ cần tháo các thùng chứa.
Docker-soạn thảo hợp nhất các container thành một mạng ảo, cách ly với Internet, trong đó các container tìm thấy nhau bằng tên miền.
Việc kiểm tra được điều khiển bởi tập lệnh shell. Để chạy thử nghiệm trên Windows, chúng tôi sử dụng git-bash. Vì vậy, một tập lệnh là đủ cho cả Windows và Linux. Git và Docker được cài đặt bởi tất cả các nhà phát triển dự án. Khi cài Git trên Windows thì git-bash được cài đặt sẵn nên ai cũng có.
Kịch bản thực hiện các bước sau:
- Xây dựng hình ảnh docker
docker-compose build - Khởi chạy cơ sở dữ liệu và LocalStack
docker-compose up -d <контейнер> - Di chuyển cơ sở dữ liệu và chuẩn bị LocalStack
docker-compose run <контейнер> - Khởi chạy dịch vụ đang thử nghiệm
docker-compose up -d <сервис> - Chạy thử nghiệm (Newman)
- Dừng tất cả các container
docker-compose down - Đăng kết quả trong Slack
Chúng tôi có một cuộc trò chuyện trong đó các tin nhắn có dấu kiểm màu xanh lục hoặc chữ thập đỏ và liên kết đến nhật ký sẽ xuất hiện.
Các hình ảnh Docker sau đây có liên quan đến các bước sau:
- Dịch vụ đang được thử nghiệm có hình ảnh giống như dịch vụ sản xuất. Cấu hình cho thử nghiệm là thông qua các biến môi trường.
- Đối với Postgres, Redis và LocalStack, hình ảnh tạo sẵn từ Docker Hub được sử dụng. Ngoài ra còn có các hình ảnh làm sẵn cho Liquibase và Newman. Chúng tôi xây dựng hệ thống của mình dựa trên bộ xương của họ, thêm các tập tin của chúng tôi vào đó.
- Để chuẩn bị LocalStack, bạn sử dụng hình ảnh AWS CLI được tạo sẵn và tạo một hình ảnh chứa tập lệnh dựa trên nó.
Sử dụng , bạn không cần phải xây dựng hình ảnh Docker chỉ để thêm tệp vào vùng chứa. Tuy nhiên, khối lượng không phù hợp với môi trường của chúng tôi vì bản thân các tác vụ Gitlab CI chạy trong vùng chứa. Bạn có thể điều khiển Docker từ vùng chứa như vậy, nhưng các tập chỉ gắn kết các thư mục từ hệ thống máy chủ chứ không phải từ vùng chứa khác.
Những vấn đề bạn có thể gặp phải
Chờ đợi sự sẵn sàng
Khi một container có dịch vụ đang chạy, điều này không có nghĩa là nó sẵn sàng chấp nhận kết nối. Bạn phải đợi kết nối tiếp tục.
Vấn đề này đôi khi được giải quyết bằng cách sử dụng tập lệnh , chờ cơ hội thiết lập kết nối TCP. Tuy nhiên, LocalStack có thể đưa ra lỗi 502 Bad Gateway. Ngoài ra, nó bao gồm nhiều dịch vụ và nếu một trong số chúng đã sẵn sàng, điều này không nói lên điều gì về những dịch vụ khác.
phán quyết: Các tập lệnh cung cấp LocalStack chờ phản hồi 200 từ cả SQS và SNS.
Xung đột nhiệm vụ song song
Nhiều thử nghiệm có thể chạy đồng thời trên cùng một máy chủ Docker, vì vậy tên vùng chứa và tên mạng phải là duy nhất. Hơn nữa, các thử nghiệm từ các nhánh khác nhau của cùng một dịch vụ cũng có thể chạy đồng thời, do đó, việc ghi tên chúng vào mỗi tệp soạn thảo là không đủ.
phán quyết: Tập lệnh đặt biến COMPOSE_PROJECT_NAME thành một giá trị duy nhất.
Tính năng của Windows
Có một số điều tôi muốn chỉ ra khi sử dụng Docker trên Windows, vì những trải nghiệm này rất quan trọng để hiểu lý do tại sao xảy ra lỗi.
- Các tập lệnh Shell trong vùng chứa phải có phần cuối dòng Linux.
Ký hiệu CR shell là lỗi cú pháp. Thật khó để biết từ thông báo lỗi rằng đây là trường hợp. Khi chỉnh sửa các tập lệnh như vậy trên Windows, bạn cần một trình soạn thảo văn bản thích hợp. Ngoài ra, hệ thống kiểm soát phiên bản phải được cấu hình đúng cách.
Đây là cách git được cấu hình:
git config core.autocrlf input- Git-bash mô phỏng các thư mục Linux tiêu chuẩn và khi gọi một tệp exe (bao gồm docker.exe), sẽ thay thế các đường dẫn Linux tuyệt đối bằng các đường dẫn Windows. Tuy nhiên, điều này không có ý nghĩa đối với các đường dẫn không có trên máy cục bộ (hoặc các đường dẫn trong vùng chứa). Hành vi này không thể bị vô hiệu hóa.
phán quyết: thêm dấu gạch chéo bổ sung vào đầu đường dẫn: //bin thay vì /bin. Linux hiểu những đường dẫn như vậy; đối với nó, nhiều dấu gạch chéo giống nhau. Nhưng git-bash không nhận ra những đường dẫn như vậy và không cố gắng chuyển đổi chúng.
Đầu ra nhật ký
Khi chạy thử nghiệm, tôi muốn xem nhật ký của cả Newman và dịch vụ đang được thử nghiệm. Vì các sự kiện của các nhật ký này được kết nối với nhau nên việc kết hợp chúng trong một bảng điều khiển sẽ thuận tiện hơn nhiều so với hai tệp riêng biệt. Newman ra mắt thông qua docker-compose chạy, và do đó đầu ra của nó kết thúc trong bảng điều khiển. Tất cả những gì còn lại là đảm bảo rằng đầu ra của dịch vụ cũng được đưa đến đó.
Giải pháp ban đầu là làm docker-soạn lên không có cờ -d, nhưng bằng cách sử dụng các khả năng của shell, hãy gửi quy trình này xuống nền:
docker-compose up <service> &Điều này hoạt động cho đến khi cần gửi nhật ký từ Docker đến dịch vụ của bên thứ ba. docker-soạn lên đã dừng xuất nhật ký ra bàn điều khiển. Tuy nhiên nhóm đã làm việc docker đính kèm.
phán quyết:
docker attach --no-stdin ${COMPOSE_PROJECT_NAME}_<сервис>_1 &Xung đột định danh trong quá trình lặp lại thử nghiệm
Các thử nghiệm được chạy trong một số lần lặp lại. Cơ sở dữ liệu không bị xóa. Các bản ghi trong cơ sở dữ liệu có ID duy nhất. Nếu chúng tôi ghi lại các ID cụ thể trong các yêu cầu, chúng tôi sẽ gặp xung đột ở lần lặp thứ hai.
Để tránh điều đó, ID phải là duy nhất hoặc tất cả các đối tượng được tạo bởi thử nghiệm phải bị xóa. Một số đối tượng không thể bị xóa do yêu cầu.
phán quyết: tạo GUID bằng tập lệnh Postman.
var uuid = require('uuid');
var myid = uuid.v4();
pm.environment.set('myUUID', myid);Sau đó sử dụng ký hiệu trong truy vấn {{myUUID}}, sẽ được thay thế bằng giá trị của biến.
Cộng tác qua LocalStack
Nếu dịch vụ đang được kiểm tra đọc hoặc ghi vào hàng đợi SQS thì để xác minh điều này, bản thân dịch vụ kiểm tra cũng phải hoạt động với hàng đợi này.
phán quyết: yêu cầu từ Postman tới LocalStack.
API dịch vụ AWS được ghi lại, cho phép thực hiện truy vấn mà không cần SDK.
Nếu một dịch vụ ghi vào hàng đợi thì chúng tôi sẽ đọc nó và kiểm tra nội dung của tin nhắn.
Nếu dịch vụ gửi tin nhắn tới SNS, ở giai đoạn chuẩn bị, LocalStack cũng tạo hàng đợi và đăng ký chủ đề SNS này. Sau đó, tất cả đi đến những gì đã được mô tả ở trên.
Nếu dịch vụ cần đọc tin nhắn từ hàng đợi thì ở bước kiểm tra trước chúng ta sẽ ghi tin nhắn này vào hàng đợi.
Kiểm tra các yêu cầu HTTP có nguồn gốc từ microservice đang được kiểm tra
Một số dịch vụ hoạt động qua HTTP bằng thứ gì đó không phải AWS và một số tính năng AWS không được triển khai trong LocalStack.
phán quyết: trong những trường hợp này nó có thể giúp ích , có hình ảnh được tạo sẵn trong . Các yêu cầu và phản hồi dự kiến đối với chúng được định cấu hình bằng yêu cầu HTTP. API được ghi lại nên chúng tôi gửi yêu cầu từ Người đưa thư.
Kiểm tra xác thực và ủy quyền OAuth
Chúng tôi sử dụng OAuth và . Thử nghiệm yêu cầu nhà cung cấp OAuth mà chúng tôi có thể chạy cục bộ.
Tất cả sự tương tác giữa dịch vụ và nhà cung cấp OAuth đều bắt nguồn từ hai yêu cầu: thứ nhất, cấu hình được yêu cầu /.well-known/openid-configuration, sau đó khóa chung (JWKS) được yêu cầu tại địa chỉ từ cấu hình. Tất cả điều này là nội dung tĩnh.
phán quyết: Nhà cung cấp OAuth thử nghiệm của chúng tôi là một máy chủ nội dung tĩnh và hai tệp trên đó. Mã thông báo được tạo một lần và được cam kết với Git.
Các tính năng của thử nghiệm SignalR
Người đưa thư không hoạt động với websockets. Một công cụ đặc biệt đã được tạo để kiểm tra SignalR.
Ứng dụng khách SignalR có thể không chỉ là một trình duyệt. Có một thư viện máy khách cho nó trong .NET Core. Máy khách, được viết bằng .NET Core, thiết lập kết nối, được xác thực và chờ một chuỗi thông báo cụ thể. Nếu nhận được một tin nhắn không mong muốn hoặc mất kết nối, máy khách sẽ thoát với mã là 1. Nếu nhận được tin nhắn mong đợi cuối cùng, máy khách sẽ thoát với mã là 0.
Newman làm việc đồng thời với khách hàng. Một số ứng dụng khách được khởi chạy để kiểm tra xem tin nhắn có được gửi đến tất cả những người cần chúng hay không.

Để chạy nhiều máy khách, hãy sử dụng tùy chọn --tỉ lệ trên dòng lệnh docker-compose.
Trước khi chạy, tập lệnh Postman đợi tất cả máy khách thiết lập kết nối.
Chúng tôi đã gặp phải vấn đề chờ kết nối. Nhưng đã có máy chủ và đây là máy khách. Một cách tiếp cận khác là cần thiết.
phán quyết: client trong container sử dụng cơ chế để thông báo cho tập lệnh trên máy chủ về trạng thái của nó. Máy khách tạo một tệp tại một đường dẫn cụ thể, chẳng hạn như /healthcheck, ngay khi kết nối được thiết lập. Tập lệnh HealthCheck trong tệp docker trông như thế này:
HEALTHCHECK --interval=3s CMD if [ ! -e /healthcheck ]; then false; fiĐội thanh tra bến tàu Hiển thị trạng thái bình thường, tình trạng sức khỏe và mã thoát cho container.
Sau khi Newman hoàn thành, tập lệnh sẽ kiểm tra xem tất cả các vùng chứa của máy khách đã chấm dứt hay chưa, với mã 0.
Hạnh phúc tồn tại
Sau khi khắc phục được những khó khăn nêu trên, chúng tôi đã có bộ bài test chạy ổn định. Trong thử nghiệm, mỗi dịch vụ hoạt động như một đơn vị duy nhất, tương tác với cơ sở dữ liệu và Amazon LocalStack.
Các thử nghiệm này bảo vệ một nhóm gồm hơn 30 nhà phát triển khỏi các lỗi trong một ứng dụng có sự tương tác phức tạp của hơn 10 vi dịch vụ được triển khai thường xuyên.
Nguồn: www.habr.com
