Splitting the Monolith — Chiến lược & pattern (Building Microservices)
Mở đầu
Ảnh: Ludovic Delot — Pexels
Sau khi đã hiểu microservices là gì ở Chương 1, câu hỏi tiếp theo hiện ra: làm sao để biến cái monolith đang chạy ngon lành thành一堆 dịch vụ nhỏ? Chương 3 của Building Microservices dành trọn để trả lời câu hỏi đó — và nó không đơn thuần là chuyện "cắt nhỏ code ra".
Việc split monolith là một trong những quyết định kiến trúc khó nhất mà một team backend phải đối mặt. Split sai cách còn đau hơn không split — bạn mất sự đơn giản của monolith nhưng lại chẳng có lợi ích gì từ distributed system.
Strategic Splitting — Dẫn lối bằng Bounded Context
Ảnh: Daniil Komov — Pexels
Trước khi đụng đến code, bạn cần một chiến lược. Và chiến lược đó đến từ Domain-Driven Design (DDD). Sam Newman nhấn mạnh: bounded context là đơn vị cơ bản để xác định ranh giới giữa các service.
Bounded context là một ranh giới logic trong domain — bên trong nó, mọi concept đều có một nghĩa duy nhất, nhất quán. Ví dụ: "Customer" trong context của Sales khác với "Customer" trong context của Support. Sales quan tâm đến lead, pipeline, conversion; Support quan tâm đến ticket, SLA, resolution. Gộp chung chúng vào một model là nguồn gốc của mọi đau khổ.
Business capability là một góc nhìn khác: nhìn vào tổ chức, xem team làm gì, rồi map nó thành service. Nếu team có một trách nhiệm rõ ràng (ví dụ: xử lý payment), đó là một ứng cử viên service tốt.
Quy tắc vàng: một service nên có một lý do duy nhất để thay đổi — tương tự Single Responsibility Principle, nhưng ở cấp độ service thay vì class.
Strangler Fig Pattern — Cách split an toàn nhất
Ảnh: Steve A Johnson — Pexels
Không ai khuyên bạn tắt monolith, viết lại từ đầu rồi bật lên. Đó là cách nhanh nhất để phá sản dự án. Thay vào đó, Strangler Fig pattern — lấy cảm hứng từ cây si (fig tree) siết dần cây chủ — là cách được đề xuất.
Cách hoạt động: bạn đặt một proxy/API gateway trước monolith. Mỗi lần bạn implement xong một chức năng ở service mới, bạn route request đó qua service mới thay vì monolith. Dần dần, monolith trở nên nhỏ hơn, và một ngày đẹp trời bạn tắt nó đi.
Ưu điểm lớn nhất: rollback dễ dàng. Nếu service mới có vấn đề, bạn chỉ cần route lại về monolith — không mất dữ liệu, không downtime.
Tách Database — Phần đau đớn nhất
Phần lớn sức nặng của việc split monolith nằm ở tách database. Trong monolith, mọi thứ đều chung một database — join thoải mái, transaction ACID đầy đủ. Khi split, mỗi service có database riêng, và bạn mất hai thứ quý giá đó.
Database decomposition patterns:
- Database view / foreign key: tạm thời cho phép service A truy vấn trực tiếp database của service B thông qua view. Nhanh, nhưng tạo tight coupling — chỉ dùng trong giai đoạn chuyển đổi.
- Database synchronization: dùng Change Data Capture (CDC) hoặc event log để đồng bộ dữ liệu giữa các database. Phức tạp hơn nhưng đúng pattern.
- Service API: service A gọi API của service B để lấy dữ liệu. Đây là đích đến cuối cùng — mỗi service chỉ expose API, không expose database.
Xử lý transaction: Khi một business operation trải qua nhiều service, bạn không thể dùng distributed transaction (2PC) — nó quá chậm và không scale. Thay vào đó, dùng Saga pattern: chia transaction lớn thành chuỗi local transaction, mỗi bước có compensating transaction để rollback nếu xảy ra lỗi.
Báo cáo & analytics: Một câu hỏi kinh điển: "Làm sao để join dữ liệu từ nhiều service để làm báo cáo?" Câu trả lời là CQRS — tách command (ghi) và query (đọc). Dữ liệu từ các service được tổng hợp vào một reporting database riêng, tối ưu cho read pattern.
Key Takeaways
- Bounded context là đơn vị cơ bản để xác định ranh giới service — bắt đầu từ DDD, không bắt đầu từ code
- Strangler Fig là pattern an toàn nhất để migrate từ monolith sang microservices — không rewrite từ đầu
- Tách database là thử thách lớn nhất — không có ACID transaction giữa các service, phải dùng Saga pattern
- CQRS giúp giải quyết bài toán reporting khi data phân tán
- Split là một quá trình, không phải sự kiện — có thể mất nhiều tháng, nhiều năm
📋 Phụ lục thuật ngữ
- Bounded Context — Ranh giới logic trong DDD, nơi mọi thuật ngữ có một nghĩa duy nhất
- Strangler Fig — Pattern migrate dần dần từ hệ thống cũ sang mới qua proxy/gateway
- Saga — Chuỗi local transaction có compensating action để đảm bảo consistency trong distributed system
- CQRS — Command Query Responsibility Segregation: tách biệt model ghi và model đọc
- CDC — Change Data Capture: cơ chế bắt sự thay đổi dữ liệu ở database để đồng bộ sang hệ thống khác
- Business Capability — Khả năng kinh doanh của tổ chức, dùng để xác định ranh giới service
Kết
Splitting the monolith không phải là bài toán kỹ thuật thuần tuý — nó là bài toán về tổ chức, về domain knowledge, và về khả năng quản lý complexity. Bounded context từ DDD cho bạn công cụ để suy nghĩ về ranh giới; Strangler Fig cho bạn con đường an toàn để đi; và database decomposition là cái giá bạn phải trả cho sự độc lập của service.
Chương 4 sẽ bàn về giao tiếp giữa các service — REST, gRPC, messaging, và cách chọn đúng công cụ cho đúng bài toán. Nếu bạn đã xác định được ranh giới (bounded context), chương tiếp theo sẽ giúp bạn quyết định các service nói chuyện với nhau bằng gì.