CQRS ที่ไม่มี DDD และไม่มี (หรือมี?) ES - โมเดลการเขียนคืออะไรและโมเดลการอ่านคืออะไร


11

เท่าที่ฉันเข้าใจแนวคิดที่สำคัญเบื้องหลัง CQRS คือมีตัวแบบข้อมูล 2 แบบสำหรับจัดการคำสั่งและแบบสอบถาม สิ่งเหล่านี้เรียกว่า "write model" และ "read model"

ลองพิจารณาตัวอย่างของการโคลนใบสมัคร Twitter นี่คือคำสั่ง:

  • ผู้ใช้สามารถลงทะเบียนด้วยตนเอง CreateUserCommand(string username)ส่งเสียงUserCreatedEvent
  • ผู้ใช้สามารถติดตามผู้ใช้รายอื่น FollowUserCommand(int userAId, int userBId)ส่งเสียงUserFollowedEvent
  • ผู้ใช้สามารถสร้างโพสต์ CreatePostCommand(int userId, string text)ส่งเสียงPostCreatedEvent

ขณะที่ฉันใช้คำว่า "เหตุการณ์" ด้านบนฉันไม่ได้หมายถึงเหตุการณ์ 'การจัดหากิจกรรม' ฉันแค่หมายถึงสัญญาณที่ทริกเกอร์อ่านการอัพเดตโมเดล ฉันไม่มีที่เก็บเหตุการณ์และจนถึงตอนนี้ต้องการที่จะมีสมาธิกับ CQRS เอง

และนี่คือแบบสอบถาม:

  • ผู้ใช้ต้องเห็นรายการโพสต์ GetPostsQuery(int userId)
  • ผู้ใช้ต้องการดูรายชื่อผู้ติดตาม GetFollowersQuery(int userId)
  • ผู้ใช้ต้องการดูรายชื่อผู้ใช้ที่ติดตาม GetFollowedUsersQuery(int userId)
  • ผู้ใช้ต้องการเห็น "ฟีดเพื่อน" - บันทึกกิจกรรมของเพื่อนทั้งหมด ("เพื่อนของคุณจอห์นเพิ่งสร้างโพสต์ใหม่") GetFriedFeedRecordsQuery(int userId)

เพื่อจัดการCreateUserCommandฉันจำเป็นต้องรู้ว่าผู้ใช้ดังกล่าวมีอยู่แล้ว ดังนั้น ณ จุดนี้ฉันรู้ว่ารูปแบบการเขียนของฉันควรมีรายชื่อผู้ใช้ทั้งหมด

ในการจัดการFollowUserCommandฉันจำเป็นต้องรู้ว่า userA ติดตาม userB อยู่แล้วหรือไม่ ณ จุดนี้ฉันต้องการให้โมเดลการเขียนของฉันมีรายการการเชื่อมต่อที่ผู้ใช้ติดตามผู้ใช้ทั้งหมด

และในที่สุดการจัดการCreatePostCommandฉันไม่คิดว่าฉันต้องการอะไรอีกแล้วเพราะฉันไม่มีคำสั่งเช่นUpdatePostCommandนั้น หากฉันมีสิ่งเหล่านั้นฉันจะต้องตรวจสอบให้แน่ใจว่ามีโพสต์อยู่แล้วดังนั้นฉันจะต้องมีรายการโพสต์ทั้งหมด แต่เนื่องจากฉันไม่มีข้อกำหนดนี้ฉันจึงไม่จำเป็นต้องติดตามโพสต์ทั้งหมด

คำถาม # 1 : จริง ๆ แล้วใช้คำว่า "แบบจำลองการเขียน" ถูกต้องหรือไม่ หรือ "การเขียนแบบ" มักจะยืนสำหรับ "ที่จัดเก็บเหตุการณ์" ในกรณีของ ES หรือไม่? ถ้าเป็นเช่นนั้นจะมีการแบ่งแยกระหว่างข้อมูลที่ฉันต้องการจัดการคำสั่งและข้อมูลที่ฉันต้องการในการจัดการแบบสอบถามหรือไม่?

ในการจัดการGetPostsQueryฉันต้องการรายการโพสต์ทั้งหมด ซึ่งหมายความว่าโมเดลการอ่านของฉันควรมีรายการโพสต์ทั้งหมด PostCreatedEventฉันจะรักษารูปแบบนี้โดยการฟัง

เพื่อจัดการทั้งสองGetFollowersQueryและGetFollowedUsersQueryฉันจะต้องมีรายการการเชื่อมต่อทั้งหมดระหว่างผู้ใช้ UserFollowedEventเพื่อรักษารูปแบบนี้ฉันจะฟัง นี่คือคำถาม # 2 : มันใช้ได้จริงไหมถ้าฉันใช้รายชื่อการเชื่อมต่อของแบบจำลองการเขียนที่นี่? หรือฉันควรสร้างแบบจำลองการอ่านแยกต่างหากเพราะในอนาคตฉันอาจต้องมีรายละเอียดมากกว่าแบบจำลองการเขียน?

ในที่สุดการจัดการGetFriendFeedRecordsQueryฉันจะต้อง:

  • ฟัง UserFollowedEvent
  • ฟัง PostCreatedEvent
  • รู้ว่าผู้ใช้คนใดที่ติดตามผู้ใช้คนอื่น

หากผู้ใช้กตามผู้ใช้ขและผู้ใช้ขเริ่มติดตามผู้ใช้คเรกคอร์ดต่อไปนี้ควรปรากฏขึ้น:

  • สำหรับผู้ใช้ A: "คุณเป็นเพื่อนผู้ใช้ B เพิ่งเริ่มติดตามผู้ใช้ C"
  • สำหรับผู้ใช้ B: "คุณเพิ่งเริ่มติดตามผู้ใช้ C"
  • สำหรับผู้ใช้ C: "ผู้ใช้ B กำลังติดตามคุณ"

นี่คือคำถาม # 3 : ฉันควรใช้รุ่นใดเพื่อรับรายการการเชื่อมต่อ ฉันควรใช้แบบจำลองการเขียนหรือไม่? ฉันควรใช้แบบอ่าน - GetFollowersQuery/ GetFollowedUsersQuery? หรือฉันควรสร้างGetFriendFeedRecordsQueryแบบจำลองเองจัดการUserFollowedEventและดูแลรายการการเชื่อมต่อทั้งหมดของตัวเอง?


โปรดทราบว่าในระบบ CQRS ตัวแบบข้อมูลแบบสอบถามและตัวแบบข้อมูลคำสั่งอาจใช้ข้อมูลจากฐานข้อมูลที่แตกต่างกัน และทั้งสองรุ่นอาจมีชีวิตอยู่อย่างอิสระซึ่งกันและกัน (แอพที่ต่างกัน) ที่ถูกกล่าวว่าคำตอบคือ "ขึ้นอยู่กับ" (ตามปกติ) มันอาจน่าสนใจ
Laiv

คำตอบ:


7

เกร็กยัง (2010)

CQRS เป็นเพียงการสร้างวัตถุสองชิ้นที่ก่อนหน้านี้มีเพียงวัตถุเดียว

หากคุณคิดในแง่ของการแยกคำสั่งการค้นหาของเบอร์ทรานด์เมเยอร์คุณสามารถนึกถึงโมเดลว่ามีอินเทอร์เฟซที่แตกต่างกันสองแบบอันหนึ่งที่รองรับคำสั่งและอันที่รองรับการสืบค้น

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 {
    // ...
}

ความเข้าใจของ Greg Young ก็คือคุณสามารถแยกสิ่งนี้ออกเป็นวัตถุสองชิ้นแยกกันได้

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

เมื่อแยกวัตถุแล้วคุณมีตัวเลือกในการแยกโครงสร้างข้อมูลที่เก็บสถานะวัตถุในหน่วยความจำเพื่อให้คุณสามารถปรับให้เหมาะสมสำหรับแต่ละกรณี หรือจัดเก็บ / คงสถานะการอ่านแยกจากสถานะเขียน

คำถาม # 1: จริง ๆ แล้วใช้คำว่า "แบบจำลองการเขียน" ถูกต้องหรือไม่ หรือ "การเขียนแบบ" มักจะยืนสำหรับ "ที่จัดเก็บเหตุการณ์" ในกรณีของ ES หรือไม่? ถ้าเป็นเช่นนั้นจะมีการแบ่งแยกระหว่างข้อมูลที่ฉันต้องการจัดการคำสั่งและข้อมูลที่ฉันต้องการในการจัดการแบบสอบถามหรือไม่?

คำว่า WriteModel มักจะเข้าใจว่าเป็นการนำเสนอรูปแบบที่ไม่แน่นอน (เช่น: วัตถุไม่ใช่ที่เก็บถาวร)

TL: DR: ฉันคิดว่าการใช้ของคุณดี

นี่คือคำถาม # 2: มันใช้ได้จริงไหมถ้าฉันใช้รายชื่อการเชื่อมต่อของแบบจำลองการเขียนที่นี่?

นั่นคือ "ดี" - ish แนวคิดไม่มีอะไรผิดปกติกับโมเดลการอ่านและโมเดลการเขียนที่แชร์โครงสร้างเดียวกัน

ในทางปฏิบัติเนื่องจากการเขียนไปยังโมเดลไม่ใช่ปรมาณูอาจมีปัญหาเมื่อเธรดหนึ่งพยายามแก้ไขสถานะของโมเดลในขณะที่เธรดที่สองพยายามอ่าน

นี่คือคำถาม # 3: ฉันควรใช้รุ่นใดเพื่อรับรายการการเชื่อมต่อ ฉันควรใช้แบบจำลองการเขียนหรือไม่? ฉันควรใช้แบบจำลองการอ่าน - GetFollowersQuery / GetFollowedUsersQuery หรือไม่ หรือฉันควรทำให้รุ่นของ GetFriendFeedRecordsQuery จัดการกับ UserFollowedEvent และรักษารายการการเชื่อมต่อทั้งหมดของตัวเอง?

การใช้แบบจำลองการเขียนเป็นคำตอบที่ผิด

การเขียนแบบจำลองการอ่านหลายแบบโดยที่แต่ละรุ่นจะถูกปรับให้เหมาะกับการใช้งานเฉพาะนั้นมีความสมเหตุสมผลอย่างสมบูรณ์ เราพูดว่า "แบบจำลองการอ่าน" แต่เป็นที่เข้าใจกันว่าอาจมีรูปแบบการอ่านจำนวนมากซึ่งแต่ละแบบได้รับการปรับให้เหมาะสมสำหรับกรณีการใช้งานเฉพาะและไม่ได้ใช้กรณีที่ไม่เหมาะสม

ตัวอย่างเช่นคุณอาจตัดสินใจใช้ที่เก็บคีย์ - ค่าเพื่อสนับสนุนเคียวรีบางอย่างและฐานข้อมูลกราฟสำหรับเคียวรีอื่นหรือฐานข้อมูลเชิงสัมพันธ์ที่โมเดลเคียวรีนั้นเหมาะสม ม้าสำหรับหลักสูตร

ในสถานการณ์เฉพาะของคุณที่คุณยังคงเรียนรู้รูปแบบข้อเสนอแนะของฉันจะทำให้การออกแบบของคุณ "ง่าย" - มีรูปแบบการอ่านหนึ่งที่ไม่ใช้โครงสร้างข้อมูลของรูปแบบการเขียน


1

ฉันขอแนะนำให้คุณพิจารณาว่าคุณมีรูปแบบข้อมูลแนวคิดหนึ่งรูปแบบ

จากนั้นโมเดลการเขียนจะเป็นรูปธรรมของโมเดลข้อมูลที่ปรับให้เหมาะสมสำหรับการอัพเดตธุรกรรม บางครั้งสิ่งนี้หมายถึงฐานข้อมูลเชิงสัมพันธ์ปกติ

แบบจำลองการอ่านเป็นรูปธรรมของโมเดลข้อมูลเดียวกันที่ปรับให้เหมาะสมสำหรับการดำเนินการแบบสอบถามที่แอปพลิเคชันของคุณต้องการ มันอาจจะยังคงเป็นฐานข้อมูลเชิงสัมพันธ์แม้ว่าจะมีการทำให้เป็นปกติในการจัดการคิวรีที่มีการรวมน้อยกว่า


(# 1) สำหรับ CQRS โมเดลการเขียนไม่จำเป็นต้องเป็นที่จัดเก็บกิจกรรม

(# 2) ฉันไม่คาดหวังว่ารูปแบบการอ่านจะเก็บสิ่งใดก็ตามที่ไม่ได้อยู่ในรูปแบบการเขียนสำหรับหนึ่งเนื่องจากใน CQRS ไม่มีใครอัปเดตรูปแบบการอ่านยกเว้นกลไกการส่งต่อที่ทำให้รุ่นอ่านสอดคล้องกับการเปลี่ยนแปลง เขียนแบบ

(# 3) ใน CQRS ข้อความค้นหาควรตรงกับรูปแบบการอ่าน คุณสามารถทำสิ่งต่าง ๆ ได้: ไม่เป็นไรไม่ได้ติดตาม CQRS


โดยสรุปมีรูปแบบข้อมูลแนวคิดเพียงรูปแบบเดียวเท่านั้น CQRS แยกเครือข่ายคำสั่งและความสามารถออกจากเครือข่ายแบบสอบถามและความสามารถ เนื่องจากการแยกโมเดลการเขียนและโมเดลการอ่านสามารถโฮสต์โดยใช้เทคโนโลยีที่แตกต่างกันอย่างมากมายเพื่อเพิ่มประสิทธิภาพ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.