CQRS không có DDD và không có (hoặc có?) ES - mô hình viết là gì và mô hình đọc là gì?


11

Theo tôi hiểu, ý tưởng lớn đằng sau CQRS là có 2 mô hình dữ liệu khác nhau để xử lý các lệnh và truy vấn. Chúng được gọi là "mô hình viết" và "mô hình đọc".

Hãy xem xét một ví dụ về bản sao ứng dụng Twitter. Dưới đây là các lệnh:

  • Người dùng có thể tự đăng ký. CreateUserCommand(string username)phát raUserCreatedEvent
  • Người dùng có thể theo dõi người dùng khác. FollowUserCommand(int userAId, int userBId)phát raUserFollowedEvent
  • Người dùng có thể tạo bài viết. CreatePostCommand(int userId, string text)phát raPostCreatedEvent

Trong khi tôi sử dụng thuật ngữ "sự kiện" ở trên, tôi không có nghĩa là các sự kiện 'tìm nguồn cung ứng sự kiện'. Tôi chỉ có nghĩa là tín hiệu kích hoạt đọc cập nhật mô hình. Tôi không có cửa hàng sự kiện và cho đến nay muốn tập trung vào chính CQRS.

Và đây là các truy vấn:

  • Một người dùng cần phải xem danh sách các bài viết của nó. GetPostsQuery(int userId)
  • Một người dùng cần phải xem danh sách những người theo dõi nó. GetFollowersQuery(int userId)
  • Một người dùng cần phải xem danh sách người dùng mà nó theo dõi. GetFollowedUsersQuery(int userId)
  • Người dùng cần xem "nguồn cấp dữ liệu bạn bè" - nhật ký của tất cả các hoạt động của bạn bè họ ("bạn của bạn John vừa tạo một bài đăng mới"). GetFriedFeedRecordsQuery(int userId)

Để xử lý CreateUserCommandtôi cần biết nếu một người dùng như vậy đã tồn tại. Vì vậy, tại thời điểm này tôi biết rằng mô hình viết của tôi nên có một danh sách tất cả người dùng.

Để xử lý FollowUserCommandtôi cần biết nếu userA đã theo dõi userB hay chưa. Tại thời điểm này, tôi muốn mô hình viết của mình có một danh sách tất cả các kết nối người dùng theo dõi người dùng.

Và cuối cùng, để xử lý CreatePostCommandtôi không nghĩ mình cần gì nữa, vì tôi không có lệnh như thế nào UpdatePostCommand. Nếu tôi có những thứ đó, tôi sẽ cần đảm bảo rằng bài đăng đó tồn tại, vì vậy tôi sẽ cần một danh sách tất cả các bài viết. Nhưng vì tôi không có yêu cầu này, tôi không cần phải theo dõi tất cả các bài viết.

Câu hỏi số 1 : có thực sự đúng khi sử dụng thuật ngữ "mô hình viết" theo cách tôi sử dụng không? Hay "mô hình viết" luôn luôn thay thế cho "cửa hàng sự kiện" trong trường hợp ES? Nếu vậy, có bất kỳ loại tách giữa dữ liệu tôi cần để xử lý các lệnh và dữ liệu tôi cần để xử lý các truy vấn không?

Để xử lý GetPostsQuery, tôi sẽ cần một danh sách tất cả các bài viết. Điều này có nghĩa là mô hình đọc của tôi nên có một danh sách tất cả các bài viết. Tôi sẽ duy trì mô hình này bằng cách lắng nghe PostCreatedEvent.

Để xử lý cả hai GetFollowersQueryGetFollowedUsersQuery, tôi sẽ cần một danh sách tất cả các kết nối giữa những người dùng. Để duy trì mô hình này, tôi sẽ lắng nghe UserFollowedEvent. Đây là câu hỏi số 2 : thực tế có ổn không nếu tôi sử dụng danh sách kết nối của mô hình ghi ở đây? Hoặc tốt hơn tôi nên tạo một mô hình đọc riêng biệt, bởi vì trong tương lai tôi có thể cần nó để có nhiều chi tiết hơn mô hình viết?

Cuối cùng, để xử lý GetFriendFeedRecordsQuerytôi sẽ cần:

  • Nghe UserFollowedEvent
  • Nghe PostCreatedEvent
  • Biết người dùng nào theo dõi người dùng khác

Nếu người dùng A theo dõi người dùng B và người dùng B bắt đầu theo dõi người dùng C, các bản ghi sau sẽ xuất hiện:

  • Đối với người dùng A: "Bạn bè người dùng B vừa bắt đầu theo dõi người dùng C"
  • Đối với người dùng B: "Bạn vừa mới bắt đầu theo dõi người dùng C"
  • Đối với người dùng C: "Người dùng B hiện đang theo dõi bạn"

Đây là Câu hỏi số 3 : Tôi nên sử dụng mô hình nào để có danh sách các kết nối? Có nên sử dụng mô hình viết? Tôi có nên sử dụng mô hình đọc - GetFollowersQuery/ GetFollowedUsersQuery? Hoặc tôi nên làm cho GetFriendFeedRecordsQuerymô hình tự xử lý UserFollowedEventvà duy trì danh sách tất cả các kết nối của riêng mình?


Hãy nhớ rằng trong các hệ thống CQRS, mô hình dữ liệu truy vấn và mô hình dữ liệu lệnh có thể tiêu thụ dữ liệu từ các cơ sở dữ liệu khác nhau. Và cả hai mô hình có thể sống độc lập với nhau (các ứng dụng khác nhau). Điều đó đang được nói, câu trả lời là "phụ thuộc" (như thường lệ). Nó có thể quan tâm
Laiv

Câu trả lời:


7

Greg Young (2010)

CQRS chỉ đơn giản là việc tạo ra hai đối tượng mà trước đây chỉ có một.

Nếu bạn nghĩ theo cách phân tách truy vấn lệnh của Bertrand Meyer , bạn có thể nghĩ mô hình có hai giao diện riêng biệt, một giao diện hỗ trợ các lệnh và một giao diện hỗ trợ truy vấn.

interface IChangeTheModel {
    void createUser(string username)
    void followUser(int userAId, int userBId)
    void createPost(int userId, string text)
}

interface IDontChangeTheModel {
    Iterable<Post> getPosts(int userId)
    Iterable<Follower> getFollowers(int userId)
    Iterable<FollowedUser> getFollowedUsers(int userId)
}

class TheModel implements IChangeTheModel, IDontChangeTheModel {
    // ...
}

Cái nhìn sâu sắc của Greg Young là bạn có thể tách điều này thành hai đối tượng riêng biệt

class WriteModel implements IChangeTheModel { ... }
class ReadModel  implements IDontChangeTheModel {...}

Khi đã tách các đối tượng, bây giờ bạn có tùy chọn tách các cấu trúc dữ liệu giữ trạng thái đối tượng trong bộ nhớ, để bạn có thể tối ưu hóa cho từng trường hợp; hoặc lưu trữ / duy trì trạng thái đọc tách biệt với trạng thái ghi.

Câu hỏi số 1: có thực sự đúng khi sử dụng thuật ngữ "mô hình viết" theo cách tôi sử dụng không? Hay "mô hình viết" luôn luôn thay thế cho "cửa hàng sự kiện" trong trường hợp ES? Nếu vậy, có bất kỳ loại tách giữa dữ liệu tôi cần để xử lý các lệnh và dữ liệu tôi cần để xử lý các truy vấn không?

Thuật ngữ WriteModel thường được hiểu là đại diện có thể thay đổi của mô hình (nghĩa là: đối tượng, không phải là kho lưu trữ bền vững).

TL; DR: Tôi nghĩ rằng việc sử dụng của bạn là tốt.

Đây là câu hỏi số 2: thực tế có ổn không nếu tôi sử dụng danh sách kết nối của mô hình ghi ở đây?

Đó là "tốt" - ish. Về mặt khái niệm, không có gì sai với mô hình đọc và mô hình viết chia sẻ cùng cấu trúc.

Trong thực tế, vì ghi vào mô hình không phải là nguyên tử điển hình, có khả năng xảy ra sự cố khi một luồng đang cố gắng sửa đổi trạng thái của mô hình trong khi luồng thứ hai đang cố đọc nó.

Đây là câu hỏi số 3: tôi nên sử dụng mô hình nào để có được danh sách các kết nối? Có nên sử dụng mô hình viết? Tôi có nên sử dụng mô hình đọc - GetFollowersQuery / GetFollowedUsersQuery không? Hoặc tôi nên tự tạo mô hình GetFriendFeedRecordsQuery xử lý UserFollowedEvent và duy trì danh sách tất cả các kết nối của riêng mình?

Sử dụng mô hình viết là câu trả lời sai.

Viết nhiều mô hình đọc, trong đó mỗi mô hình được điều chỉnh cho một trường hợp sử dụng cụ thể là hoàn toàn hợp lý. Chúng tôi nói "mô hình đọc", nhưng được hiểu rằng có thể có nhiều mô hình đọc, mỗi mô hình được tối ưu hóa cho một trường hợp sử dụng cụ thể và không triển khai các trường hợp không có ý nghĩa.

Ví dụ: bạn có thể quyết định sử dụng kho lưu trữ khóa-giá trị để hỗ trợ một số truy vấn và cơ sở dữ liệu đồ thị cho các truy vấn khác hoặc cơ sở dữ liệu quan hệ nơi mô hình truy vấn đó có ý nghĩa. Ngựa cho các khóa học.

Trong trường hợp cụ thể của bạn, nơi bạn vẫn đang học mẫu, đề nghị của tôi sẽ là giữ cho thiết kế của bạn "đơn giản" - có một mô hình đọc không chia sẻ cấu trúc dữ liệu của mô hình ghi.


1

Tôi sẽ khuyến khích bạn xem xét rằng bạn có một mô hình dữ liệu khái niệm.

Sau đó, mô hình ghi là sự cụ thể hóa của mô hình dữ liệu đó được tối ưu hóa cho các cập nhật giao dịch. Đôi khi điều này có nghĩa là một cơ sở dữ liệu quan hệ chuẩn hóa.

Mô hình đọc là sự cụ thể hóa của cùng một mô hình dữ liệu được tối ưu hóa để thực hiện các truy vấn mà (các) ứng dụng của bạn cần (s). Nó vẫn có thể là một cơ sở dữ liệu quan hệ mặc dù cố tình không chuẩn hóa để xử lý các truy vấn có ít phép nối hơn.


(# 1) Đối với CQRS, mô hình ghi không phải là một cửa hàng sự kiện.

(# 2) Tôi không mong đợi mô hình đọc sẽ lưu trữ bất cứ thứ gì không có trong mô hình ghi, vì trong CQRS, không ai cập nhật mô hình đọc ngoại trừ cơ chế chuyển tiếp giữ cho mô hình đọc đồng bộ với các thay đổi đối với viết mô hình.

(# 3) Trong CQRS, các truy vấn phải đi ngược lại với mô hình đọc. Bạn có thể làm khác: không sao, chỉ cần không theo CQRS.


Tóm lại, chỉ có một mô hình dữ liệu khái niệm. CQRS tách mạng lệnh và khả năng khỏi mạng truy vấn và khả năng. Do đó, mô hình ghi và mô hình đọc có thể được lưu trữ bằng cách sử dụng các công nghệ khác nhau rất lớn để tối ưu hóa hiệu suất.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.