Skip to content

Sự tiến hóa từ Monolith đến Microservices

Lời mở đầu

Không có kiến trúc nào là "tốt nhất", chỉ có "phù hợp nhất với giai đoạn hiện tại". Từ monolith đến microservices không phải là một bước nhảy hoàn thành ngay, mà là quá trình tiến hóa dần dần theo sự tăng trưởng về quy mô nghiệp vụ và quy mô đội ngũ. Phân tách microservices quá sớm cũng nguy hiểm không kém phân tách quá muộn.

Bài viết này sẽ giúp bạn học được gì?

Sau khi học xong chương này, bạn sẽ đạt được:

  • Lộ trình tiến hóa: Hiểu bốn giai đoạn từ monolith đến microservices
  • Thời điểm phân tách: Biết khi nào nên tách, khi nào không nên tách
  • Chiến lược phân tách: Nắm vững phương pháp phân tách theo domain nghiệp vụ
  • Mô hình giao tiếp: Hiểu các lựa chọn giao tiếp đồng bộ và bất đồng bộ giữa các dịch vụ
  • Phân tách dữ liệu: Hiểu thách thức và giải pháp phân tách cơ sở dữ liệu
ChươngNội dungKhái niệm cốt lõi
Chương 1Lộ trình tiến hóa kiến trúcMonolith → Modular Monolith → SOA → Microservices
Chương 2Thời điểm và nguyên tắc phân táchĐịnh luật Conway, tự chủ đội nhóm
Chương 3Chiến lược phân táchBounded Context của DDD, Strangler Fig Pattern
Chương 4Giao tiếp giữa các dịch vụREST, gRPC, Message Queue
Chương 5Phân tách dữ liệuPhân tách DB, đồng bộ dữ liệu

1. Lộ trình tiến hóa kiến trúc

Tiến hóa kiến trúc không do công nghệ thúc đẩy, mà do quy mô tổ chức thúc đẩy. Khi đội nhóm tăng từ 5 người lên 500 người, hiệu suất cộng tác của kiến trúc monolith sẽ giảm sút nghiêm trọng.

Giai đoạnKiến trúcQuy mô đội nhómĐặc điểm
Khởi độngỨng dụng monolith1~10 ngườiTất cả mã trong một dự án, triển khai đơn giản
Tăng trưởngMonolith module hóa10~50 ngườiMã được chia theo module, nhưng vẫn triển khai cùng nhau
Mở rộngSOA (Service-Oriented)50~200 ngườiPhân tách theo line nghiệp vụ thành dịch vụ thô
Quy mô lớnMicroservices200+ ngườiDịch vụ hạt mịn, mỗi đội nhóm phát triển triển khai độc lập
Architecture Evolution Path
Click each stage to inspect its architecture characteristics
1
Monolithic architecture
2
Modular monolith
3
Service-oriented architecture
4
Microservices architecture
Monolithic architecture
All features are packaged in one application and share one database. It is simple and suitable for early rapid iteration.
User module
Order module
Payment module
Product module
Monolith app (one process)
MySQL
Suitable scale:Team < 10 people, DAU < 100k
Core challenge:Code is tightly coupled; a bug in one module may bring down the whole system

Định luật Conway

"Tổ chức thiết kế hệ thống, kiến trúc được tạo ra tương đương với cấu trúc giao tiếp của tổ chức đó." — Melvin Conway

Nói đơn giản: 3 đội nhóm làm một hệ thống, cuối cùng sẽ thành 3 dịch vụ. Bản chất của phân tách kiến trúc là phân tách tổ chức.

Định luật Conway ngược: Vì cấu trúc tổ chức quyết định kiến trúc hệ thống, nên muốn có kiến trúc như thế nào thì trước tiên hãy điều chỉnh thành cấu trúc tổ chức tương ứng. Ví dụ bạn muốn tách ra dịch vụ thanh toán độc lập, thì trước tiên hãy thành lập một đội thanh toán độc lập. Nhiều công ty phân tách microservices thất bại, không phải do vấn đề công nghệ, mà do tổ chức không điều chỉnh theo.


2. Khi nào nên tách microservices?

Không phải hệ thống nào cũng cần microservices. Tách quá sớm sẽ mang lại độ phức tạp không cần thiết.

Tín hiệuMô tảKhuyến nghị
Xung đột triển khai thường xuyênNhiều đội nhóm sửa cùng một codebase, thường xuyên xung độtCân nhắc tách
Module nào đó cần mở rộng độc lậpModule tìm kiếm cần tài nguyên gấp 10 lần các module khácCân nhắc tách
Stack công nghệ cần khác biệt hóaModule AI dùng Python, trang chính dùng JavaCân nhắc tách
Đội nhóm < 10 ngườiChi phí giao tiếp thấp, monolith là đủKhông nên tách
Nghiệp vụ còn đang trong giai đoạn khám pháNhu cầu thay đổi nhanh, ranh giới không rõ ràngKhông nên tách
Không có năng lực DevOpsKhông có CI/CD, container hóa, hệ thống giám sátKhông nên tách

3. Chiến lược phân tách

3.1 Phân tách theo domain nghiệp vụ (Bounded Context của DDD)

Bounded Context (Ngữ cảnh giới hạn) của DDD (Domain-Driven Design) là nguyên tắc chỉ đạo tốt nhất để phân tách microservices. Mỗi bounded context tương ứng với một domain nghiệp vụ độc lập, có mô hình dữ liệu và quy tắc nghiệp vụ riêng.

Bounded context là gì? Cùng một từ nhưng trong các domain nghiệp vụ khác nhau có ý nghĩa khác nhau. Ví dụ "người dùng" trong domain người dùng là thông tin đăng ký (tên, email), trong domain đơn hàng là người đặt hàng (địa chỉ nhận hàng, phương thức thanh toán), trong domain đề xuất là hồ sơ hành vi (lịch sử duyệt, tag sở thích). Bounded context chính là vạch ra một ranh giới, trong ranh giới đó, thuật ngữ và mô hình có ý nghĩa rõ ràng thống nhất.

┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│  Domain User │  │ Domain Order│  │ Domain Pay  │
│             │  │             │  │             │
│ User        │  │ Order       │  │ Payment     │
│ Profile     │  │ OrderItem   │  │ Refund      │
│ Address     │  │ Cart        │  │ Transaction │
│             │  │             │  │             │
│ User Svc    │  │ Order Svc   │  │ Payment Svc │
└──────┬──────┘  └──────┬──────┘  └──────┬──────┘
       │                │                │
       └────── API call / Event comms ───────┘
Bounded ContextThực thể cốt lõiDịch vụ tương ứng
Domain người dùngUser, Profile, AddressDịch vụ người dùng
Domain sản phẩmProduct, Category, SKUDịch vụ sản phẩm
Domain đơn hàngOrder, OrderItemDịch vụ đơn hàng
Domain thanh toánPayment, RefundDịch vụ thanh toán
Domain logisticsShipment, TrackingDịch vụ logistics

3.2 Strangler Fig Pattern (Mô hình Cây strangler fig)

Không nên viết lại toàn bộ monolith cùng một lúc, mà giống như cây strangler fig, từng bước dùng dịch vụ mới thay thế module cũ:

  1. Tạo dịch vụ mới bên ngoài monolith
  2. Thông qua proxy layer chuyển một phần lưu lượng đến dịch vụ mới
  3. Sau khi xác minh dịch vụ mới ổn định, dần dần chuyển thêm lưu lượng
  4. Cuối cùng thay thế hoàn toàn module cũ

4. Mô hình giao tiếp giữa các dịch vụ

Phương thứcGiao thứcĐặc điểmPhù hợp
RESTHTTP/JSONĐơn giản, phổ biến, ecosystem tốtAPI mở rộng, thao tác CRUD
gRPCHTTP/2 + ProtobufHiệu suất cao, kiểu mạnhGọi tần suất cao giữa các dịch vụ nội bộ
Message QueueAMQP/KafkaBất đồng bộ, tách biệt, cắt đỉnh lấp vựcThông báo sự kiện, tác vụ bất đồng bộ
GraphQLHTTP/JSONClient truy vấn theo nhu cầuTầng BFF, mobile

Lựa chọn đồng bộ vs bất đồng bộ

  • Cần trả về kết quả ngay lập tức → Đồng bộ (REST/gRPC)
  • Không cần trả về ngay → Bất đồng bộ (Message Queue)
  • Một sự kiện kích hoạt nhiều hành động → Bất đồng bộ (Publish-Subscribe)

Nguyên tắc kinh nghiệm: có thể bất đồng bộ thì bất đồng bộ, chuỗi gọi đồng bộ càng dài, hệ thống càng mong manh.


5. Phân tách dữ liệu: Phần khó nhất

Trong phân tách microservices, phần đau đớn nhất không phải phân tách mã, mà là phân tách cơ sở dữ liệu. Mỗi dịch vụ nên sở hữu cơ sở dữ liệu riêng, nhưng điều này có nghĩa là truy vấn xuyên dịch vụ trở nên khó khăn.

Thách thứcMô tảGiải pháp
JOIN xuyên dịch vụKhông thể JOIN trực tiếp bảng của hai dịch vụTruy vấn tổng hợp API, dư thừa dữ liệu
Giao dịch phân tánGiao dịch liên DB không thể dùng giao dịch cục bộSaga, bảng tin nhắn cục bộ
Nhất quán dữ liệuDữ liệu nhiều dịch vụ có thể tạm thời không nhất quánNhất quán cuối cùng, event-driven
Di chuyển dữ liệuTừ DB dùng chung sang DB độc lậpChuyển giao kép ghi, công cụ đồng bộ dữ liệu

Tổng kết

Từ monolith đến microservices là một quá trình tiến bộ, không phải một cuộc cách mạng thực hiện trong một bước.

Ôn lại các điểm chính của chương này:

  1. Lộ trình tiến hóa: Monolith → Modular Monolith → SOA → Microservices, mỗi bước đều có động lực rõ ràng
  2. Thời điểm phân tách: Quy mô đội nhóm, xung đột triển khai, nhu cầu mở rộng là tín hiệu để phân tách
  3. Chiến lược phân tách: Dùng Bounded Context của DDD để hướng dẫn phân tách, dùng Strangler Fig Pattern để di chuyển dần dần
  4. Lựa chọn giao tiếp: Có thể bất đồng bộ thì bất đồng bộ, chuỗi gọi đồng bộ càng ngắn càng tốt
  5. Phân tách dữ liệu: Khó nhất nhưng quan trọng nhất, chấp nhận nhất quán cuối cùng là sự chuyển đổi tư duy then chốt

Đọc thêm