Viết lại toàn bộ một hệ thống đang tạo doanh thu là một canh bạc: sản phẩm cũ vẫn phải thay đổi, yêu cầu tiếp tục xuất hiện và ngày chuyển đổi luôn xa hơn dự kiến. Strangler Pattern đưa ra hướng khác. Hệ thống mới được xây quanh hệ thống cũ, nhận dần từng capability qua một lớp định tuyến. Monolith vẫn phục vụ phần chưa chuyển; phần mới được đo, rollout và rollback độc lập. Khi capability cuối cùng rời đi, phần cũ tương ứng mới được gỡ bỏ.
Mô hình này không tự động biến mọi monolith thành microservices và cũng không loại bỏ rủi ro. Nó thay một lần cắt chuyển rất lớn bằng nhiều lần chuyển nhỏ có điều kiện thành công rõ ràng. Giá trị nằm ở khả năng học từ production, giữ đường lùi và giao giá trị trong suốt chương trình hiện đại hóa.
1. Khi nào nên cân nhắc Strangler Pattern?
Pattern phù hợp khi hệ thống cũ vẫn có giá trị nghiệp vụ nhưng tốc độ thay đổi, khả năng mở rộng hoặc nền tảng kỹ thuật trở thành giới hạn. Ví dụ, một module báo giá phải release cùng toàn bộ ERP; phần tìm kiếm gây tải nặng lên database giao dịch; hoặc framework hết hỗ trợ nhưng không thể dừng kinh doanh nhiều tháng để viết lại.
- Ranh giới capability có thể nhận diện và chuyển độc lập theo từng lát cắt.
- Có thể đặt gateway, facade hoặc cơ chế routing trước luồng hiện tại.
- Nhóm duy trì được cả cũ và mới trong một khoảng thời gian có giới hạn.
- Tổ chức chấp nhận đầu tư vào quan sát, kiểm thử hợp đồng và migration dữ liệu.
Nếu ứng dụng nhỏ, ít thay đổi và chi phí vận hành thấp, việc tách có thể đắt hơn lợi ích. Nếu hệ thống không có test, không có log tối thiểu và không ai hiểu luồng nghiệp vụ, bước đầu tiên nên là tạo safety net và bản đồ dependency chứ chưa phải dựng service mới.
Đừng bắt đầu bằng câu hỏi “tách thành bao nhiêu service”. Hãy bắt đầu bằng capability nào đang gây chi phí lớn và có thể chuyển với blast radius nhỏ nhất.
2. Chọn lát cắt theo capability, không theo bảng hay tầng kỹ thuật
Tách “service database”, “service utility” hoặc toàn bộ lớp controller thường giữ nguyên coupling ở dạng network call. Lát cắt tốt hơn là một capability có ngôn ngữ, quyền sở hữu dữ liệu và kết quả nghiệp vụ tương đối rõ: quản lý catalog, tính phí vận chuyển, phát hành hóa đơn hay gửi thông báo. Nó bao gồm giao diện vào, logic và dữ liệu cần thiết để hoàn thành một mục tiêu.
| Ứng viên | Điểm thuận lợi | Rủi ro cần kiểm tra |
|---|---|---|
| Thông báo email/SMS | Ít phụ thuộc transaction chính, dễ quan sát | Duplicate, retry, consent và rate limit |
| Search/read model | Chủ yếu đọc, có thể rebuild | Độ trễ đồng bộ và phân quyền kết quả |
| Catalog sản phẩm | Ranh giới nghiệp vụ thường rõ | Giá, tồn kho và khuyến mại có thể coupling |
| Thanh toán | Giá trị tách cao | Rủi ro tài chính, idempotency và reconciliation lớn |
Capability đầu tiên nên đủ thật để kiểm chứng pipeline nhưng không phải phần nguy hiểm nhất. Chọn luồng có traffic đo được, owner nghiệp vụ rõ, contract tương đối ổn định và rollback khả thi. Thành công của lát cắt đầu tiên phải tạo ra template cho routing, logging, CI/CD, secrets, alert và vận hành sự cố.
3. Đặt lớp định tuyến làm “đường khâu” giữa cũ và mới
Mọi request cần đi qua một điểm có thể quyết định capability nào do hệ thống nào xử lý. Điểm đó có thể là reverse proxy, API gateway, backend-for-frontend hoặc facade trong monolith. Routing nên dựa trên path, tenant, feature flag hoặc khóa ổn định; tránh logic tùy hứng khiến cùng một operation nhảy qua lại giữa hai bên.
route(request):
if killSwitch.enabled("new-catalog"):
return legacy.handle(request)
if cohort(request.tenantId) in rolloutCohorts:
return modernCatalog.handle(request)
return legacy.handle(request)
Gateway không nên biến thành nơi chứa nghiệp vụ mới. Nhiệm vụ của nó là xác thực ở mức biên, chuẩn hóa context, định tuyến, timeout và ghi telemetry. Quyết định giá, tồn kho hay trạng thái đơn hàng thuộc capability. Cần có correlation ID xuyên cả hai hệ thống để so sánh hành vi trong giai đoạn cùng tồn tại.
4. Contract trước, triển khai sau
Trước khi chuyển traffic, hãy ghi lại contract thực tế của monolith: request, response, mã lỗi, authorization, side effect và các trường hợp kỳ lạ mà consumer đang phụ thuộc. Tài liệu API cũ thường không đủ; cần contract test trên hành vi production đã được phép quan sát, fixture đã ẩn dữ liệu và log support ticket.
Hệ thống mới có thể có model nội bộ tốt hơn nhưng adapter phải giữ tương thích trong giai đoạn chuyển. Consumer không nên vừa đổi endpoint, vừa đổi authentication, vừa đổi semantics trong một lần. Nếu cần phiên bản mới, hãy tách migration consumer khỏi migration implementation để rollback từng phần.
- So sánh status code, header và schema, không chỉ happy-path payload.
- Chuẩn hóa timeout và hành vi khi dependency không sẵn sàng.
- Xác định rõ operation nào idempotent và key có scope thế nào.
- Không để error message nội bộ trở thành contract ngoài ý muốn.
5. Dữ liệu là phần khó nhất của migration
Hai service cùng ghi một bảng hoặc cùng sở hữu một field tạo ra ownership mơ hồ. Mục tiêu cuối cùng là mỗi capability có một nguồn dữ liệu có thẩm quyền. Trong quá trình chuyển, có thể dùng API, outbox/CDC, snapshot rồi catch-up hoặc view chỉ đọc. Direct dual write từ request handler dễ phân kỳ khi một lần ghi thành công và lần kia thất bại.
Một trình tự an toàn thường là: backfill dữ liệu lịch sử vào store mới; ghi nhận watermark; đồng bộ thay đổi sau watermark; so sánh count, checksum và invariant; cho hệ mới đọc shadow; sau đó mới chuyển quyền ghi. Nếu dữ liệu lớn, backfill phải theo batch có checkpoint, rate limit và khả năng chạy lại. Không để migration tranh tài nguyên với workload giao dịch.
Khi chuyển quyền ghi, transaction xuyên hai database thường không thực tế. Hãy commit nguồn có thẩm quyền cùng outbox event, rồi cập nhật projection/consumer theo cơ chế at-least-once. Consumer cần idempotent, xử lý out-of-order và có reconciler phát hiện sai lệch. “Eventual” phải đi kèm SLO về lag và cách UI thể hiện trạng thái chưa đồng bộ.
6. Shadow traffic, canary và tiêu chí quyết định
Shadow traffic gửi bản sao request đọc sang hệ mới nhưng response cho người dùng vẫn từ hệ cũ. Đây là cách tốt để kiểm tra compatibility và hiệu năng, miễn là đường shadow không tạo side effect. Với write, không được nhân bản thật một cách vô thức; có thể chạy validation-only hoặc so sánh quyết định trên dữ liệu đã cô lập.
Khi canary, hãy route theo cohort ổn định như tenant hoặc account để một workflow không đổi backend giữa chừng. Bắt đầu với nội bộ, tiếp theo là tenant ít rủi ro rồi tăng dần. Mỗi bước phải có thời gian quan sát và ngưỡng dừng:
- Tỷ lệ lỗi và timeout không vượt budget.
- p95/p99 latency không suy giảm ngoài ngưỡng đã thống nhất.
- Kết quả so sánh dữ liệu không có sai lệch nghiêm trọng.
- Queue lag, saturation và dependency error vẫn trong giới hạn.
- Đội vận hành có thể chẩn đoán request xuyên gateway, cũ và mới.
Feature flag cần owner, ngày hết hạn và kill switch đã kiểm thử. Một flag tồn tại vô thời hạn tạo thêm nhánh code và khiến mọi sự cố khó tái hiện.
7. Rollback không đồng nghĩa route traffic về cũ
Nếu hệ mới đã nhận quyền ghi, chỉ đổi route về monolith có thể làm mất những thay đổi mà hệ cũ chưa biết. Kế hoạch rollback phải xác định dữ liệu nào đã thay đổi, có cơ chế reverse sync hay không, và điểm nào migration trở thành forward-only. Với schema breaking hoặc nghiệp vụ tài chính, có thể cần dừng write, reconcile và thực hiện runbook có phê duyệt thay vì rollback tự động.
Trước rollout, hãy diễn tập: tắt service mới, làm broker chậm, khiến một consumer lỗi và phục hồi từ checkpoint. Đường lùi chỉ đáng tin khi đã được chạy trong môi trường gần production. Backup là lớp cuối, không phải chiến lược rollback nhanh vì restore có thể mất nhiều giờ và ảnh hưởng capability khác.
8. Tổ chức code và ownership trong giai đoạn cùng tồn tại
Monolith vẫn nhận feature và bug fix trong lúc migration diễn ra. Nếu đội hiện đại hóa tách biệt hoàn toàn với đội vận hành cũ, contract sẽ trôi và tri thức không được chuyển giao. Mỗi capability cần một owner xuyên suốt cũ–mới, backlog chung và quyết định rõ thay đổi mới được xây ở đâu.
Anti-corruption layer giúp mô hình mới không bị kéo theo tên bảng và quy tắc bất thường của legacy. Tuy nhiên adapter chỉ nên sống trong giai đoạn cần thiết. Theo dõi dependency còn lại bằng dashboard hoặc architecture test: endpoint legacy còn traffic, job nào còn đọc bảng cũ, consumer nào còn gọi API cũ. Không thể tuyên bố hoàn thành chỉ vì route chính đã đổi.
9. Điều kiện gỡ bỏ phần cũ
Decommission là deliverable, không phải việc dọn dẹp “khi có thời gian”. Trước khi xóa code hoặc database cũ, cần chứng minh không còn traffic hợp lệ, batch/job đã chuyển, dữ liệu được lưu theo policy và runbook support đã cập nhật.
- Đóng route cũ bằng cảnh báo rồi quan sát mọi caller bị bỏ sót.
- Dừng write, giữ read-only trong cửa sổ an toàn nếu cần.
- Xuất hoặc archive dữ liệu theo retention và legal hold.
- Thu hồi secrets, network rule, cron, queue và quyền truy cập.
- Xóa dashboard/alert không còn giá trị và cập nhật ownership.
- Gỡ code, dependency và hạ tầng sau khi có bằng chứng hậu kiểm.
Chi phí kép chỉ kết thúc khi hệ cũ thật sự được tắt. Một chương trình liên tục tạo service mới nhưng không xóa đường cũ sẽ làm tổng độ phức tạp tăng lên thay vì giảm xuống.
10. Những sai lầm phổ biến
- Tách theo tầng kỹ thuật: tạo distributed monolith với nhiều network hop.
- Big-bang dữ liệu ở phút cuối: không có rehearsal, checkpoint hoặc reconciliation.
- Dual write không kiểm soát: hai nguồn sự thật phân kỳ âm thầm.
- Không có SLO migration: không biết khi nào rollout nên dừng hay tiếp tục.
- Giữ đường cũ vô thời hạn: flag, adapter và hạ tầng kép trở thành trạng thái vĩnh viễn.
- Ép mọi capability thành microservice: tăng chi phí vận hành mà không giảm coupling.
Checklist triển khai từng capability
- Boundary, owner và nguồn dữ liệu có thẩm quyền đã rõ.
- Contract và behavior legacy có test bảo vệ.
- Routing có cohort ổn định, kill switch và telemetry.
- Backfill, CDC/outbox, checkpoint và reconciliation đã diễn tập.
- Canary có metric, ngưỡng dừng và thời gian quan sát.
- Rollback/forward recovery bao gồm cả dữ liệu, không chỉ traffic.
- Tiêu chí decommission và ngày gỡ bỏ đã có owner.
Kết luận
Strangler Pattern giúp hiện đại hóa hệ thống bằng chuỗi thay đổi nhỏ có thể đo và đảo chiều, thay vì đặt toàn bộ doanh nghiệp vào một lần viết lại. Thành công không đến từ việc dựng nhiều service, mà từ ranh giới capability tốt, contract được bảo vệ, quyền sở hữu dữ liệu rõ, rollout có bằng chứng và kỷ luật gỡ bỏ legacy. Khi mỗi lát cắt vừa giao giá trị mới vừa giảm một phần hệ cũ, chương trình migration mới thật sự tiến về đích thay vì chỉ xây thêm một hệ thống song song.




Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.