Bài viết
/
[Design Pattern] Ambassador Pattern

Tuesday, March 25th 2025

[Design Pattern] Ambassador Pattern

Bài toán

  • Bạn được giao nhiệm vụ maintain một ứng dụng đang chạy ổn định trên production và có mã nguồn cũ, được xây dựng từ lâu trước đây. Yêu cầu mới cho hệ thống này là thêm các chức năng như điều hướng (routing) tới các dịch vụ bên ngoài, đo lường hiệu suất (monitoring, logging), tối ưu truy vấn (caching, circuit breaking) và khả năng cập nhật các cấu hình mạng. Thật khó nếu như ta phải đọc hiểu toàn bộ source code và viết thêm các chức năng không nằm trong business logic này vào trong, điều này ngoài tiềm năng rủi ro đến sự ổn định hiện tại còn gây thêm khó khăn cho đội maintain sau này.

  • Một vấn đề khác thường gặp là khi cần truy cập vào các dịch vụ bên thứ ba với các cách truy cập khác nhau. Thông thường, chúng ta phải viết các đoạn mã khác nhau để truy cập vào từng service. Ví dụ, hệ thống của chúng ta sử dụng cả Memcached và Redis làm bộ nhớ cache. Với Memcached, việc truy cập không yêu cầu bước xác thực, nhưng với Redis thì có. Một cách đơn giản, ta có thể cấu hình request interceptor cho trường hợp này. Nhưng khi ngày càng nhiều services với cấu hình khác nhau, ta không thể cấu hình chung trong một request, sẽ làm code trở nên rối và khó bảo trì.

Vậy, giải pháp cho các bài toán trên là gì?

Giải pháp

Câu trả lời là sử dụng Ambassador Pattern. Trước tiên, cùng mình làm rõ những khái niệm cơ bản có liên quan đến pattern này.

Proxy

Proxy trong tiếng anh có nghĩa là “người được ủy nhiệm, ủy quyền”.

Proxy là một server có nhiệm vụ chuyển tiếp và kiểm soát thông tin giữa client và server. Proxy gồm 1 địa chỉ IP và một port để truy cập cố định. Trong thực tế có nhiều loại Proxy được sử dụng cho từng mục đích khác nhau. Trong bài viết này, chúng ta sẽ tập trung vào 2 khái niệm là Forward proxyReverse proxy

  • Forward proxy: Thường được gọi tắt là proxy, là một proxy trung gian giữa client và internet. Khi client gửi yêu cầu, proxy sẽ thay mặt client gửi request -> [proxy] -> internet -> server, nhận phản hồi và gửi lại cho client.

  • Reverse proxy: ngược lại so với forward proxy, đây là proxy nằm giữa internet và server: client -> internet -> [reverse proxy] -> server.

Hình 1: Proxies structure
Hình 1: Proxies structure

Ambassador

Ambassador Pattern là mẫu thiết kế đóng vai trò tương tự như một Forward proxy cho hệ thống của mình, đứng trung gian thành: ứng dụng -> [ambassador] -> dịch vụ bên ngoài.

Mục đích của nó là cung cấp một giao diện thống nhất để ứng dụng có thể dễ dàng truy cập vào các dịch vụ ngoài hệ thống.

Hình 2: Ambassador architecture
Hình 2: Ambassador architecture

Có thể thấy như trong kiến trúc của Ambassador,

  • Application code có vai trò như client gửi truy vấn đến service thông qua proxy là Ambassador.
  • Ambassador có vai trò như proxy, nhận yêu cầu từ App và chuyển tiếp đến service.

Ambassador có thể giải quyết bài toán chúng ta đặt ra ở đầu bài viết (retry, monitor, logging, routing,...) mà không cần can thiệp vào mã nguồn của ứng dụng, việc phát triển mới hay sửa chữa lỗi trở nên dễ dàng hơn.

Adapter

Có thể thấy Ambassador là pattern khá đơn giản đúng không nào, tiếp theo ta cùng mở rộng hơn với Adapter Pattern nhé.

Đây là pattern gần như tương tự với Ambassador, đóng vai trò là một Reverse proxy trong hệ thống, tức luồng truy cập sẽ như sau: services/requests -> [adapter] -> ứng dụng.

Vậy Adapter ứng dụng trong trường hợp nào?

Bài toán này thường xuất hiện trong các hệ thống Microservice, chúng ta bắt đầu phát triển ứng dụng bằng một ngôn ngữ cụ thể. Khi hệ thống cần mở rộng, chúng ta có thể nhận ra rằng ngôn ngữ hiện tại không phù hợp cho dịch vụ mới và cần sử dụng một ngôn ngữ khác.

Khi hệ thống mở rộng hơn nữa, chúng ta có thể có nhiều dịch vụ với các ngôn ngữ khác nhau. Sự không đồng nhất giữa các ngôn ngữ này tạo ra khó khăn trong việc truy cập và tương tác giữa các dịch vụ.

Ví dụ nếu ta cần giám sát các Service nhưng Metric(các giá trị đo lường hiệu suất) của từng Service tạo ra lại khác nhau (format, value) thì khiến các công cụ giám sát rất khó để thu thập dữ liệu.

Lúc này, Adapter Pattern cung cấp một giao diện thống nhất để các ứng dụng bên ngoài có thể dễ dàng truy cập vào các dịch vụ qua Adapter. Việc này giúp chúng ta không cần quan tâm đến cách truy cập cụ thể của từng dịch vụ, mà chỉ cần biết rằng chúng ta có thể truy cập chúng thông qua Adapter một cách thống nhất. Trong trường hợp giám sát, chúng ta có thể sử dụng Adapter để chuyển đổi các Metric khác nhau thành một định dạng thống nhất để công cụ giám sát có thể thu thập dữ liệu một cách hiệu quả. Adapter Container là một giải pháp phù hợp cho vấn đề này.

Hình 3: Sự khác nhau giữa Ambassador và Adapter
Hình 3: Sự khác nhau giữa Ambassador và Adapter

Phân biệt Ambassador và Adapter

Vậy thì sự khác nhau giữa Ambassador và Adapter là gì khi dương như chúng có cùng 1 chức năng và chỉ khác nhau về hướng truy cập?

TrườngAmbassadorAdapter
Mục đíchProxy các kết nối ra bên ngoài (External Connections)Chuyển đổi / rewrite dữ liệu đầu ra (Rewrite Data Output)
Vai tròĐứng giữa app và dịch vụ mạng (Network Service)Đứng giữa app và hệ thống nhận dữ liệu
Tính chấtChỉ cần cấu hình để sử dụng (Config-driven)Có thể biến đổi / chỉnh sửa dữ liệu
Chức năngService Discovery, Retry, Timeout, Circuit Breaker, Load Balancing, TLS, Auth, Rate LimitFormat data, Field mapping, Schema transform, Chuẩn hóa output

Vấn đề và cân nhắc

  • Do Ambassador là một proxy, thêm 1 tầng trung gian, nhược điểm của nó sẽ làm tăng độ trễ của hệ thống.
  • Khi đưa các tính năng tổng quát như retry vào Ambassador, nó có thể tự động gửi lại request khi lỗi. Nhưng việc retry không phải lúc nào cũng an toàn, ngoại lệ khi thao tác là idempotent → gọi lại nhiều lần cũng không làm thay đổi thêm trạng thái.

Do vậy, ta có thể thiết kế cơ chế để client gửi được ngữ cảnh (context) cho Ambassador và Ambassador phản hồi lại (thường nằm trong HTTP headers). Ví dụ: Header yêu cầu không retry → Ambassador sẽ không gửi lại request. Header chỉ định số lần retry tối đa → Ambassador retry theo giới hạn đó.

  • Bạn cần quyết định Ambassador sẽ chạy theo kiểu dùng chung hay riêng cho từng client để tối ưu tài nguyên.

Ứng dụng

Khi nào nên sử dụng Ambassador Pattern?

  • Cần xây dựng bộ tính năng kết nối dùng chung cho nhiều ngôn ngữ / framework

VD: Khi có nhiều app viết bằng khác nhau (Java, Node, Go…), ta đưa logic kết nối (retry, auth, discovery…) ra proxy dùng chung, khỏi phải viết lại ở từng app.

  • Cần chuyển giao xử lý kết nối cho team hạ tầng

VD: Dev app chỉ lo business logic, còn các vấn đề network (timeout, TLS, retry…) do team infra quản lý qua proxy.

  • Cần hỗ trợ app cũ / khó sửa đổi kết nối cloud hoặc cluster

VD: Với hệ thống cũ khó chỉnh code, ta đặt proxy bên ngoài để bổ sung tính năng kết nối (service mesh, cloud routing…) mà không cần sửa app.

Kết luận

Ambassador Pattern là một giải pháp hiệu quả để giải quyết các vấn đề liên quan đến kết nối trong hệ thống Microservice. Nó giúp chúng ta xây dựng một bộ tính năng kết nối dùng chung cho nhiều ngôn ngữ / framework, chuyển giao xử lý kết nối cho team hạ tầng và hỗ trợ các ứng dụng cũ / khó sửa đổi kết nối cloud hoặc cluster. Hy vọng qua bài viết này, bạn đã hiểu rõ hơn về Ambassador Pattern và có thể áp dụng nó vào dự án của mình.

Tham khảo