Anti-corruption layer và strangler fig: cách gắn AI vào hệ thống cũ của khách hàng mà không làm hỏng nó
Hệ thống cũ của khách không cần được viết lại từ đầu. Lớp AI mới cũng không nên học theo những quy ước lộn xộn của nó.
Tóm tắt nhanh
- Anti-corruption layer dịch giữa hai hệ thống hiểu dữ liệu theo hai cách khác nhau, nhờ đó lớp AI mới giữ được thiết kế riêng còn hệ thống cũ không phải sửa.
- Strangler fig đặt một façade chặn request và chuyển dần từng chức năng sang dịch vụ mới. Caller không biết việc migrate đang diễn ra.
- Cả hai lớp đều làm tăng độ trễ và có thể thành điểm lỗi, nên cần retry, circuit breaker, correlation ID, và một kế hoạch gỡ bỏ chúng.
Tuần thứ hai ở chỗ khách hàng, bạn mở database của hệ thống quản lý bảo hành mà họ dùng hơn mười năm nay. Cột TRANG_THAI chỉ chứa ba chữ cái C, D, H. Ngày tháng lưu dạng chuỗi.
Cột MO_TA thì vừa có mô tả lỗi của khách vừa có ghi chú nội bộ của nhân viên. Trong khi đó khách muốn agent AI phân loại yêu cầu bảo hành ngay trong quý này.
Phần lớn engineer sẽ rơi vào một trong hai cái bẫy. Cái bẫy thứ nhất là cho agent đọc thẳng TRANG_THAI = 'D', và từ đó mọi prompt, mọi eval, mọi schema đều phải “biết” D nghĩa là gì. Cái bẫy thứ hai là đề xuất viết lại cả hệ thống, một dự án mà khách sẽ không bao giờ duyệt.
Có hai pattern giúp bạn đi giữa hai cái bẫy này: anti-corruption layer (ACL) và strangler fig. Nếu công việc FDE đưa bạn đến một khách hàng có hệ thống cũ không ai dám động vào, nắm chắc hai pattern này trước buổi họp kiến trúc đầu tiên sẽ giúp bạn đưa ra một kế hoạch mà khách dám duyệt.
Vì sao hệ thống cũ “lây” sang code mới?
Tài liệu kiến trúc Azure của Microsoft gọi đúng tên vấn đề: khi hệ thống mới phải nói chuyện được với hệ thống cũ, nó bị buộc phải theo ít nhất một phần API hoặc ngữ nghĩa của hệ thống cũ. Không ai quyết định làm vậy. Nó cứ từ từ xảy ra, mỗi lần thêm một câu if status == 'D'.
ACL là cách chặn chuyện đó lại. Eric Evans mô tả pattern này lần đầu trong cuốn Domain-Driven Design: một lớp facade hoặc adapter nằm giữa hai hệ thống hiểu dữ liệu theo hai cách khác nhau, có nhiệm vụ dịch request của bên này sang ngôn ngữ của bên kia.
Nhờ vậy, theo Microsoft, một hệ thống được giữ nguyên mà thiết kế và công nghệ của hệ thống còn lại không bị ảnh hưởng.
Trong một dự án AI, “hệ thống còn lại” chính là phần bạn xây: schema mà agent nhận vào, bộ eval, các tool definition. Bạn muốn chúng nói ngôn ngữ của nghiệp vụ, kiểu pending, in_progress, closed, chứ không phải ngôn ngữ của một database thiết kế từ năm nào đó.
Viết ACL cho hệ thống bảo hành như thế nào?
Quay lại ví dụ ở đầu bài. Sau khi hỏi phòng chăm sóc khách hàng, bạn biết C là “chờ xử lý”, D là “đang xử lý”, H là “hoàn tất”. Ghi chú nội bộ luôn bắt đầu bằng [NB]. Đây là phần lõi của lớp dịch:
from dataclasses import dataclass
from datetime import date, datetime
STATUS_MAP = {"C": "pending", "D": "in_progress", "H": "closed"}
@dataclass
class WarrantyClaim:
claim_id: str
status: str
created_on: date
customer_text: str
internal_notes: list[str]
class LegacyFormatError(ValueError):
pass
def to_domain(row: dict) -> WarrantyClaim:
code = (row.get("TRANG_THAI") or "").strip()
if code not in STATUS_MAP:
raise LegacyFormatError(f"Mã trạng thái lạ: {code!r} (id={row.get('MA_BH')})")
lines = (row.get("MO_TA") or "").splitlines()
return WarrantyClaim(
claim_id=row["MA_BH"],
status=STATUS_MAP[code],
created_on=datetime.strptime(row["NGAY_TAO"], "%d/%m/%Y").date(),
customer_text="\n".join(l for l in lines if not l.startswith("[NB]")),
internal_notes=[l[4:].strip() for l in lines if l.startswith("[NB]")],
)
Có ba điều nên để ý. Thứ duy nhất agent nhìn thấy là WarrantyClaim, nó không biết C, D, H tồn tại.
Mã trạng thái lạ hoặc trống làm hàm báo lỗi ngay ở ranh giới thay vì lọt vào prompt, đúng như Microsoft khuyên là phải validate input tại ranh giới.
Ngoài ra ghi chú nội bộ được tách riêng, nên bạn có thể quyết định không đưa chúng cho model.
Hàm này không chứa thứ gì liên quan đến nghiệp vụ. Nó không quyết định yêu cầu nào được ưu tiên hay yêu cầu nào phải chuyển cho kỹ thuật viên. Microsoft dặn rõ là không đặt business rule hay orchestration trong lớp này.
Còn khi AI phải thay hẳn một chức năng cũ?
ACL giúp lớp AI đọc được dữ liệu cũ. Nhưng thường khách muốn nhiều hơn thế: việc phân loại lỗi đang do một màn hình nhập tay đảm nhận, và họ muốn AI làm thay. Đây là lúc dùng strangler fig.
Martin Fowler cho rằng thay một hệ thống lớn trong một lần thường thất bại, và cách ông cùng đồng nghiệp chọn là hiện đại hóa dần dần. Mỗi lần chỉ thay một phần nhỏ thì rủi ro của mỗi lần đưa phần mềm mới vào cũng nhỏ. Người dùng cũng không phải đợi hết một dự án thay thế kéo dài nhiều năm mới có tính năng mới.
Cơ chế của strangler fig, theo Microsoft, là một façade (proxy) chặn các request đi vào hệ thống cũ rồi chuyển từng request sang hệ thống cũ hoặc sang dịch vụ mới.
Caller không biết việc migrate đang diễn ra. AWS cũng nói tương tự: vì caller không phải sửa gì khi lời gọi được chuyển hướng ngầm, rủi ro chuyển đổi và nguy cơ gián đoạn kinh doanh đều giảm.
Áp vào ví dụ bảo hành, có thể hình dung façade làm việc như sau. Request POST /claims/classify trước đây đi thẳng vào hệ thống cũ, nay đi qua façade.
Façade đọc một feature flag: với một nhóm chi nhánh thí điểm, request được chuyển sang dịch vụ AI, dữ liệu đi qua to_domain trước. Các chi nhánh còn lại vẫn chạy như cũ.
Có sự cố thì bạn tắt flag, không phải deploy lại gì.
Microsoft khuyên dùng ACL trong suốt quá trình strangler fig để quản lý phụ thuộc giữa hệ thống cũ và mới. Không có lớp adapter đó, phụ thuộc chéo có thể làm hỏng component, hoặc buộc hệ thống mới phải theo quy ước cũ. Hai pattern này đi với nhau: façade quyết định request đi đâu, ACL lo việc dịch khi request sang phía mới.
Phải hỏi gì trước khi hứa với khách?
Strangler fig có điều kiện tiên quyết. Theo Microsoft, pattern này không phải lựa chọn tốt nếu bạn không chặn được request, hoặc không truy cập được source code hệ thống cũ, vì muốn tắt chức năng đã chuyển và đổi hướng các lời gọi nội bộ thì phải sửa được code cũ.
Vì vậy, trong tuần đầu, bạn cần hỏi khách hai câu: request đi vào hệ thống qua đâu, và ai giữ source code.
Nếu câu trả lời là “logic nằm hết trong stored procedure, không ai dám sửa”, vẫn còn cách. Microsoft gợi ý dùng trigger hoặc change data capture để chính database gửi sự kiện sang dịch vụ mới, không cần đổi code ứng dụng.
Chẳng hạn mỗi khi có dòng mới trong bảng bảo hành, CDC đẩy sự kiện sang hàng đợi, dịch vụ AI nhận sự kiện qua ACL rồi ghi kết quả gợi ý vào một bảng riêng. Hệ thống cũ vẫn chạy nguyên vẹn, còn bạn có dữ liệu thật để chứng minh dịch vụ mới trước khi xin quyền chuyển hướng request.
Những lỗi hay gặp nhất
Lỗi đầu tiên là quên rằng mỗi lớp mới đều có giá. Microsoft nói ACL làm tăng độ trễ cho lời gọi giữa hai hệ thống và thêm một dịch vụ phải vận hành. Nếu agent đã mất vài giây cho mỗi lần gọi model, bạn cần đo xem lớp dịch cộng thêm bao nhiêu trước khi khách tự phát hiện ra.
Lỗi thứ hai là để façade thành nút thắt. Microsoft cảnh báo façade không được trở thành điểm lỗi duy nhất hay nút cổ chai hiệu năng. AWS nhắc rằng ACL cũng là một điểm có thể hỏng, nên cần retry và circuit breaker. Khi dịch vụ AI chậm, circuit breaker phải trả request về hệ thống cũ thay vì để người dùng chờ.
Lỗi thứ ba là không quan sát được gì. Khi một yêu cầu bảo hành bị phân loại sai, bạn cần lần theo nó qua façade, ACL và model. Vì thế mọi request nên mang một correlation ID và log có cấu trúc ngay từ ngày đầu.
Lỗi cuối cùng là coi lớp tạm như lớp vĩnh viễn. AWS khuyên nếu ACL chỉ là giải pháp tạm thời thì ghi nó vào danh sách nợ kỹ thuật và gỡ nó đi khi mọi caller đã chuyển xong.
Nếu bạn không ở lại dự án đến lúc đó, hãy ghi rõ điều kiện gỡ ACL trong tài liệu bàn giao để đội của khách biết khi nào được xóa nó.
Thể hiện kỹ năng này trong CV thế nào?
Khi viết CV ứng tuyển FDE, hãy kể lại kinh nghiệm làm việc với hệ thống cũ theo trình tự: hệ thống cũ là gì, bạn chặn request ở đâu, chức năng nào được chuyển trước, và làm cách nào để quay lại được ngay khi có sự cố.
Một câu như “chuyển dần việc phân loại sang dịch vụ mới qua façade có feature flag, không phải sửa caller” thuyết phục hơn nhiều so với “có kinh nghiệm microservices”.
Nếu chưa từng làm việc này, bạn có thể tự dựng một bài tập: lấy một ứng dụng CRUD cũ, dựng một proxy phía trước, rồi chuyển một endpoint sang dịch vụ mới có ACL đi kèm.
Khách hàng không cần nghe bạn nói hệ thống của họ cũ. Điều họ cần là một kế hoạch cho phép AI đi vào từng phần một và rút ra được bất cứ lúc nào.