MÔ HÌNH CQRS: NGHỆ THUẬT "CHIA ĐÔI CON ĐƯỜNG" ĐỌC VÀ GHI
trong các hệ thống Microservices có quy mô lớn, khi lượng truy cập (traffic) tăng phi mã, việc dùng chung một mô hình cơ sở dữ liệu truyền thống cho cả việc ghi dữ liệu (tạo đơn, sửa tài khoản) lẫn đọc dữ liệu (xem danh sách, thống kê báo cáo) sẽ nhanh chóng đẩy hệ thống vào nút thắt cổ chai (bottleneck).
Để giải quyết triệt để bài toán này, các kiến trúc sư phần mềm thường áp dụng một mẫu thiết kế (design pattern) đỉnh cao: CQRS (Command Query Responsibility Segregation - Phân tách trách nhiệm Truy vấn và Ghi).
Hãy cùng mổ xẻ xem CQRS là gì, tại sao nó lại sinh ra và cách nó vận hành trong kiến trúc Microservices.
1. Bản Chất Của CQRS Là Gì? (The What)
Tên gọi CQRS đã nói lên toàn bộ ý tưởng cốt lõi của nó: Tách biệt hoàn toàn (Segregation) mô hình xử lý lệnh ghi (Commands) ra khỏi mô hình xử lý truy vấn đọc (Queries).
Trong mô hình CRUD truyền thống (Create, Read, Update, Delete):
- Chúng ta thường dùng chung một Database (ví dụ: một bảng MySQL
Orders) và dùng chung một đối tượng Model cho cả hai việc: vừa dùng để insert/update dữ liệu, vừa dùng để select dữ liệu lên màn hình.
Trong mô hình CQRS, tư duy đó bị đập vỡ và chia làm hai nhánh độc lập:
-
Bên Ghi (Command Side):
-
Nhiệm vụ: Xử lý các tác vụ làm thay đổi trạng thái hệ thống (Tạo mới, Sửa, Xóa).
-
Đặc điểm: Ưu tiên tính toàn vẹn dữ liệu (Data Integrity), áp dụng strict validation, chuẩn hóa ACID của database.
-
-
Bên Đọc (Query Side):
-
Nhiệm vụ: Chỉ chuyên lo việc trả dữ liệu về cho người dùng xem (SELECT, Filter, Phân trang, Báo cáo).
-
Đặc điểm: Ưu tiên tốc độ tối đa (High Performance). Không bao giờ có logic nghiệp vụ phức tạp ở đây.
-
2. Sự Khác Biệt Giữa Command và Query Trong Kiến Trúc
Để hiểu rõ hơn sự tách biệt này, hãy nhìn vào quy tắc kinh điển:
-
Commands (Hành động thay đổi): Thường trả về
void(hoặc chỉ báo thành công/thất bại). Nó không được phép trả về dữ liệu phức tạp cho client xem. Ví dụ:CreateOrderCommand,UpdateUserProfileCommand. -
Queries (Hành động đọc dữ liệu): Hoàn toàn không được phép làm thay đổi trạng thái của dữ liệu (No Side Effects). Nó chỉ nhận tham số và trả về DTO (Data Transfer Object) cho tầng giao diện. Ví dụ:
GetOrderDetailsQuery,GetListProductsQuery.
3. Tại Sao CQRS Lại Là "Vũ Khí Hủy Diệt" Trong Microservices?
Khi kết hợp CQRS với kiến trúc Microservices (đặc biệt trong các hệ thống thương mại điện tử lớn như Hasaki hay các hệ thống tài chính), nó mang lại những lợi ích khổng lồ:
A. Tối ưu hóa hiệu năng độc lập (Asymmetric Scaling)
Trong thực tế, tỷ lệ Đọc (Read) luôn lớn hơn gấp nhiều lần so với tỷ lệ Ghi (Write) (ví dụ: 100 người xem sản phẩm thì mới có 1 người bấm mua).
-
Với CQRS, bạn có thể thiết kế cụm server đọc scale-out thành 10 instances, sử dụng các cơ sở dữ liệu tối ưu cho việc đọc siêu tốc như Redis, Elasticsearch, hoặc Read-Replicas của MySQL.
-
Trong khi đó, cụm server ghi có thể giữ nguyên cấu hình gọn nhẹ để đảm bảo an toàn giao dịch.
B. Sử dụng đúng loại Database cho từng mục đích (Polyglot Persistence)
-
Bên Ghi (Write Model): Thường dùng cơ sở dữ liệu quan hệ truyền thống (Relational DB như PostgreSQL, MySQL) để đảm bảo ràng buộc khóa ngoại và tính toàn vẹn giao dịch (ACID).
-
Bên Đọc (Read Model): Có thể dùng NoSQL (MongoDB) hoặc lưu dạng phẳng (Denormalized tables) để khi gọi câu lệnh SELECT không cần phải
JOINhàng chục bảng rườm rà, giúp tốc độ trả về dữ liệu đạt mức mili-giây.
C. Đồng bộ dữ liệu bất đồng thường qua Event Bus (Eventual Consistency)
Câu hỏi đặt ra: Nếu tách thành 2 database đọc và ghi riêng biệt, làm sao bên Đọc biết được dữ liệu mới khi bên Ghi vừa tạo đơn?
-
Câu trả lời nằm ở cơ chế Event-driven Architecture. Khi bên Ghi hoàn tất việc lưu đơn hàng, nó sẽ bắn ra một sự kiện (ví dụ:
OrderCreatedEvent) thông qua Message Broker (như Kafka hoặc RabbitMQ). -
Bên Đọc sẽ lắng nghe sự kiện đó và tự động cập nhật lại cơ sở dữ liệu chuyên đọc của mình. Trạng thái đồng bộ này diễn ra trong thời gian cực ngắn gọi là Eventual Consistency (Nhất quán cuối cùng).
4. Những Cái Bẫy Cần Lưu Ý Khi Dùng CQRS
Mặc dù rất mạnh mẽ, CQRS cũng có mặt trái của nó. Các chuyên gia thường cảnh báo: Đừng lạm dụng CQRS cho mọi microservice!
-
Độ phức tạp tăng vọt (Over-engineering): Nếu hệ thống của bạn là một ứng dụng nhỏ, CRUD đơn giản, việc áp dụng CQRS sẽ biến mã nguồn trở nên cồng kềnh gấp nhiều lần do phải viết riêng các Command Handler, Query Handler và cơ chế đồng bộ dữ liệu.
-
Độ trễ đồng bộ (Eventual Consistency lag): Người dùng vừa bấm nút đổi tên tài khoản, nhưng khi load lại trang ngay lập tức lại thấy tên cũ (do event đồng bộ chưa chạy tới kịp). Điều này đòi hỏi thiết kế trải nghiệm người dùng (UX) phải khéo léo.
💡 Lời Kết
CQRS không chỉ đơn thuần là một mô hình code, mà nó là tư duy phân định rõ ràng giữa "Hành động" và "Nhìn nhận". Trong thế giới Microservices, khi quy mô dữ liệu và bài toán hiệu năng chạm ngưỡng giới hạn, CQRS chính là chìa khóa vàng giúp hệ thống vừa chịu tải cực lớn ở phía đọc, vừa đảm bảo an toàn tuyệt đối ở phía ghi!
All rights reserved