CQRS pattern trong mô hình microservices
Command Query Responsibility Segregation (CQRS)
Mở đầu
Command Query Responsibility Segregation (CQRS) là một mô hình thiết kế trong đó việc ghi dữ liệu và đọc dữ liệu từ database được tách thành hai mô hình riêng biệt. Cách tiếp cận này giúp hệ thống dễ dàng mở rộng, tối ưu hiệu năng và nâng cao khả năng bảo trì. Hãy cùng khám phá chi tiết về CQRS trong bài viết này.
Trong mô hình truyền thống, một data model sẽ đảm nhận cả hai việc đọc và ghi. Hướng tiếp cận này rất phù hợp với cái hành động cơ bản: create, read, update và delete (CRUD).
Khi ứng dụng phát triển, việc tối ưu đồng thời các thao tác đọc và ghi trên cùng một mô hình dữ liệu trở nên ngày càng khó khăn. Các thao tác đọc và ghi thường có yêu cầu khác nhau về hiệu năng và khả năng mở rộng. Kiến trúc CRUD truyền thống không tính đến sự bất cân xứng này, dẫn đến một số thách thức sau:
Data mismatch
Lock contention
Performance problem
Security challenges
Giải pháp
Sư dụng mô hình CQRS để tách biệt việc ghi ( commands) và đọc ( queries). Command update data, queries retrieve data. CQRS rất hiệu quả trong bài toán tách biệt giữa việc đọc và ghi.
Commands: Đại diện cho một business task thay vì low-level data updates. Phương pháp này giúp dễ dàng nắm bắt ý định của user và điều chỉnh các commands phù hợp với qui trình kinh doanh.
Queries: Không bao giờ thay đổi data, nó chỉ chuyển đổi data theo format thuận tiện nào đó mà không chứa bất kì nghiệp vụ nào.
Có hai phương pháp triển khai CQRS, mỗi phương pháp có ưu nhược điểm riêng.
Separate models in a single data store
Đây là một cách tiếp cận cơ bản của CQSR, cả việc đọc và ghi đều dùng chung một database, nhưng được logic đọc và ghi được tách riêng biệt. Phương pháp này cải thiện clarity, performance, and scalability bằng cách tách từng model handle riêng biệt
Write model: thiết kế cho commands update và persist dữ liệu. Nó bao gồm cả việc validate data và domain logic. Đảm bảo data consistency bằng cách optimizing for transactional integrity và business processes.
Read model: được thiết kế để queries. Tập trung vào việc tạo ra các DTO hoặc projections data.
Ví dụ minh hoạ:
# ---------------------------------------------
# CQRS Demo: Separate Models, Single Data Store
# ---------------------------------------------
# Single data store (dictionary)
data_store = {
"products": {} # product_id -> {"name": ..., "price": ...}
}
# -----------------------
# Write Model (Commands)
# -----------------------
class ProductWriteModel:
def add_product(self, product_id, name, price):
data_store["products"][product_id] = {"name": name, "price": price}
print(f"Product added: {name} - ${price}")
def update_price(self, product_id, new_price):
if product_id in data_store["products"]:
data_store["products"][product_id]["price"] = new_price
print(f"Price updated for {data_store['products'][product_id]['name']} to ${new_price}")
else:
print("Product not found!")
# -----------------------
# Read Model (Queries)
# -----------------------
class ProductReadModel:
def list_products(self):
print("\nAvailable products:")
for pid, info in data_store["products"].items():
print(f"{pid}: {info['name']} - ${info['price']}")
def get_product(self, product_id):
return data_store["products"].get(product_id, "Product not found")
# -----------------------
# Demo
# -----------------------
if __name__ == "__main__":
# Command side
write_model = ProductWriteModel()
write_model.add_product(1, "Laptop", 1200)
write_model.add_product(2, "Phone", 800)
write_model.update_price(2, 750)
# Query side
read_model = ProductReadModel()
read_model.list_products()
print("\nGet single product:", read_model.get_product(2))
Cả hai mô hình đều sử dụng một kho dữ liệu duy nhất, nhưng trách nhiệm được tách riêng:
ProductWriteModel→ xử lý tất cả thao tác ghi (write/command)ProductReadModel→ xử lý tất cả thao tác đọc (read/query)
Mô hình đọc không chứa logic nghiệp vụ, chỉ truy xuất trạng thái hiện tại của dữ liệu.
Thiết lập này mô phỏng cách CQRS trên một cơ sở dữ liệu duy nhất, mà không cần tạo kho lưu trữ riêng biệt.
Separate models in different data stores
Đây là cách tiếp cận nâng cao hơn, việc tách biết ra hai models giúp việc. scale each model dễ dàng hơn và phù hợp với nhu cầu hơn. Và có thể sử dụng các database khác nhau cho từng model. Ví dụ document database cho việc đọc và relational data cho ghi dữ liệu.
Một vấn đề khi tách database riêng biệt là phải đảm bảo data được đồng bộ hoá (synchronized). Một cách đơn là là áp dụng kỹ thuật CDC, mình đã có một vài về chủ đề này, cách bạn có thể đọc lại tại hai bài viết sau đây, hoặc có thể sử dụng kiến trúc event-driven.
Ví dụ:
# ---------------------------------------------
# CQRS Demo: Separate Models, Different Data Stores
# ---------------------------------------------
# Write data store (Command)
write_store = {} # product_id -> {"name": ..., "price": ...}
# Read data store (Query) - could be optimized for reads
read_store = {} # product_id -> {"name": ..., "price": ..., "display_price": ...}
# -----------------------
# Write Model (Commands)
# -----------------------
class ProductWriteModel:
def add_product(self, product_id, name, price):
write_store[product_id] = {"name": name, "price": price}
# Update the read store as well
read_store[product_id] = {"name": name, "display_price": f"${price}"}
print(f"Product added: {name} - ${price}")
def update_price(self, product_id, new_price):
if product_id in write_store:
write_store[product_id]["price"] = new_price
# Update read store for queries
read_store[product_id]["display_price"] = f"${new_price}"
print(f"Price updated for {write_store[product_id]['name']} to ${new_price}")
else:
print("Product not found!")
# -----------------------
# Read Model (Queries)
# -----------------------
class ProductReadModel:
def list_products(self):
print("\nAvailable products (read model):")
for pid, info in read_store.items():
print(f"{pid}: {info['name']} - {info['display_price']}")
def get_product(self, product_id):
return read_store.get(product_id, "Product not found")
# -----------------------
# Demo
# -----------------------
if __name__ == "__main__":
# Command side
write_model = ProductWriteModel()
write_model.add_product(1, "Laptop", 1200)
write_model.add_product(2, "Phone", 800)
write_model.update_price(2, 750)
# Query side
read_model = ProductReadModel()
read_model.list_products()
print("\nGet single product:", read_model.get_product(2))
Trong ví dụ trên:
write_storechỉ phục vụ ghi dữ liệu,read_storephục vụ truy vấn.Read model có thể được tối ưu hóa cho truy vấn (ví dụ: định dạng hiển thị, summary view).
Mỗi kho dữ liệu có thể được lưu trữ trên cơ sở dữ liệu khác nhau, phục vụ hiệu năng và khả năng mở rộng.
Kết luận
CQRS là một mô hình thiết kế mạnh mẽ giúp tách biệt trách nhiệm giữa đọc và ghi dữ liệu, từ đó tối ưu hiệu năng, khả năng mở rộng và dễ bảo trì hệ thống hơn. Bằng cách áp dụng CQRS, bạn có thể xây dựng các ứng dụng linh hoạt, dễ quản lý và mở rộng theo nhu cầu thực tế.
Nếu bạn thấy bài viết hữu ích, hãy nhấn subscribe để nhận các bài viết tiếp theo về kiến trúc hệ thống và thiết kế phần mềm.
Nếu bạn có bất kỳ thắc mắc hoặc muốn thảo luận thêm, hãy để lại comment bên dưới — mình sẽ phản hồi sớm nhất có thể!
Đọc thêm:
https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs
https://learn.microsoft.com/en-us/azure/architecture/guide/architecture-styles/event-driven








Thanks for your post!!!