Branch by Abstraction & Saga — Split Monolith (Building Microservices)
Mở đầu
Ảnh: Christina Morillo — Pexels
Ở bài trước, mình đã nói về cách dùng Bounded Context và Strangler Fig Pattern để bắt đầu chia nhỏ monolith. Nhưng thực tế còn nhiều tình huống éo le hơn: làm sao để tách một module mà không block cả team? Làm sao xử lý transaction khi dữ liệu nằm rải rác nhiều service? Và frontend thì sao — backend tách xong rồi mà UI vẫn là một khối thì cũng vô ích.
Chapter 3 của Building Microservices (Sam Newman) dành hẳn một phần cho những pattern "chiến thuật" này. Bài này sẽ đi sâu vào ba pattern mạnh mẽ nhất: Branch by Abstraction, Saga và UI Composition.
Branch by Abstraction — Tạo "lớp đệm" trước khi tách
Ảnh: Scott Webb — Pexels
Strangler Fig Pattern chỉ hoạt động tốt khi bạn có thể chặn request ở một điểm duy nhất (thường là API gateway hoặc load balancer). Nhưng với những module được gọi trực tiếp từ code — một class Java trong monolith gọi sang một module khác qua interface nội bộ — Strangler Fig không giúp được gì.
Branch by Abstraction giải quyết vấn đề này:
- Tạo abstraction — thay vì gọi trực tiếp implementation cũ, bạn tạo một interface/abstraction bao quanh nó. Ví dụ: thay vì gọi
OldPaymentProcessor.process(), bạn tạo interfacePaymentProcessorvà để implementation cũ implement nó. - Viết implementation mới — tạo một implementation mới của interface đó, trỏ đến service mới (microservice). Code cũ và mới cùng tồn tại, cùng implement chung một interface.
- Switch via config/feature flag — dùng feature flag hoặc config để chọn implementation nào sẽ chạy. Bạn có thể chuyển dần dần: 10% → 50% → 100% traffic qua service mới.
- Xoá implementation cũ — khi đã chắc chắn service mới hoạt động ổn, xoá implementation cũ và cleanup abstraction nếu không cần thiết.
Pattern này cực kỳ hữu ích khi bạn không thể block development để tách monolith. Cả đội vẫn code bình thường trên monolith, trong khi nhóm infrastructure âm thầm tách module ra service riêng. Khi nào chuyển xong thì bật feature flag — rollback cũng dễ dàng.
Sam Newman gọi đây là "kỹ thuật ít được biết đến nhưng vô cùng giá trị" trong kho vũ khí splitting monolith. Trong thực tế các công ty lớn như Netflix và Uber từng dùng pattern này để migrate hàng triệu dòng code.
Saga Pattern — Xử lý transaction xuyên service
Một trong những nỗi đau lớn nhất khi tách monolith là mất đi ACID transaction. Trong monolith, bạn có thể update 3 bảng trong một transaction — commit hoặc rollback tất cả. Nhưng khi dữ liệu nằm ở 3 service khác nhau, không còn database transaction nào bao phủ được cả.
Saga là câu trả lời. Saga là một chuỗi các local transaction, mỗi bước thực hiện một操作 (operation) trên một service, và nếu bước nào fail thì chạy compensating transaction để undo bước trước đó.
Có hai cách orchestrate saga:
- Choreography-based Saga — mỗi service lắng nghe events và tự quyết định hành động tiếp theo. Service A tạo order → emit event → Service B (payment) lắng nghe, xử lý payment → emit event → Service C (shipping) nhận và xử lý ship. Nếu C fail, nó emit event fail → B lắng nghe và refund, A lắng nghe và cancel order.
- Orchestrator-based Saga — có một service "chỉ huy" (orchestrator) điều phối toàn bộ flow. Nó gọi Service A → nhận kết quả → gọi Service B → nếu B fail thì nó gọi compensating action của A. Cách này dễ manage hơn nhưng tạo central coordination point.
Ảnh: cottonbro studio — Pexels
Điểm quan trọng: saga không có isolation giữa các bước (không giống ACID). Nếu cần isolation, bạn phải dùng semantic lock hoặc compensating transaction records — một chủ đề dành riêng cho chapter sau.
UI Composition — Tách frontend cùng với backend
Tách backend mà frontend vẫn là một khối duy nhất — đó là một nửa công cuộc. UI Composition là pattern phân mảnh frontend theo cùng ranh giới với backend service.
Cách đơn giản nhất: mỗi service render HTML fragment của riêng nó, và một "page composer" (thường là API gateway hoặc một layout service) ghép chúng lại. Ví dụ:
- Service A render phần header + user info
- Service B render phần product listing
- Service C render phần giỏ hàng
Mỗi team sở hữu trọn vẹn cả backend lẫn frontend fragment của mình — đây là tinh thần của microservices: bạn build nó, bạn chạy nó.
Có thể dùng Server-Side Includes (SSI), Edge-Side Includes (ESI), hoặc iframe ở mức đơn giản nhất. Với các SPA hiện đại, Micro Frontends (single-spa, Module Federation) cũng là một dạng UI Composition cho client-side.
Key Takeaways
- Branch by Abstraction giúp tách module ngay cả khi không có single entry point — tạo interface, implement mới, switch bằng feature flag
- Saga thay thế distributed transaction trong môi trường microservices — mỗi bước có compensating action để rollback khi cần
- UI Composition đảm bảo frontend cũng được phân tách cùng ranh giới với backend — mỗi team tự chủ từ UI đến database
- Không có pattern nào là silver bullet — chọn pattern phù hợp với context: Strangler Fig cho API, Branch by Abstraction cho code nội bộ, Saga cho data consistency
📋 Phụ lục thuật ngữ
- Branch by Abstraction — kỹ thuật tạo abstraction layer để migration code dần dần, giữ code cũ và mới cùng tồn tại
- Saga — chuỗi local transaction với compensating action, đảm bảo eventual consistency xuyên service
- Compensating Transaction — thao tác undo của một bước trong saga, khôi phục trạng thái trước đó
- UI Composition — pattern kết hợp HTML fragment từ nhiều service thành một trang web hoàn chỉnh
- Micro Frontends — extension của microservices cho frontend, mỗi team sở hữu một phần UI
- Feature Flag — cơ chế bật/tắt tính năng mà không cần deploy lại code
Kết
Chapter 3 của Building Microservices không chỉ dừng lại ở "cách split" — nó dạy bạn cách split an toàn. Branch by Abstraction giúp bạn không block team, Saga giúp bạn không mất data consistency, và UI Composition giúp frontend cũng theo kịp backend.
Bài tiếp theo sẽ nói về Microservice Communication Styles — khi đã tách xong services, bọn chúng nói chuyện với nhau bằng cách nào? REST, gRPC, hay message broker?