การคืนสภาพรวมจากการฉายภาพ“ สแน็ปช็อต” มากกว่าที่จัดเก็บกิจกรรม


14

ดังนั้นตอนนี้ฉันก็เลยเริ่มมีการจัดหากิจกรรมและ CQRS มาระยะหนึ่งแล้วแม้ว่าฉันจะไม่เคยมีโอกาสใช้รูปแบบในโครงการจริง

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

สิ่งที่ฉันไม่ชัดเจนเป็นพิเศษคือเหตุผลที่คุณจะต้องคืนค่าการรวมของคุณจาก Event Store เอง

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

ฉันคิดว่านี่จะต้องเป็นเรื่องธรรมดาใน ES + CQRS ในแอพพลิเคชั่น

หากเป็นกรณีนี้ Event Store จะมีประโยชน์เฉพาะเมื่อสร้างฐานข้อมูล "เขียน" ของคุณใหม่เนื่องจากการเปลี่ยนแปลงสคีมาหรือไม่ หรือฉันกำลังพลาดอะไรที่ใหญ่กว่านี้?


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

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

ไม่มีเหตุผลที่คุณควรโหลดเอนทิตีของคุณจาก Event Store อย่างต่อเนื่อง นั่นไม่ใช่จุดประสงค์ของมัน โดยมีวัตถุประสงค์คือเพื่อให้บัญชีแยกประเภทดิบและถาวรของทุกสิ่งที่เกิดขึ้นในระบบ เอนทิตีรัฐเก็บและแบบจำลองการอ่าน denormalized สำหรับการโหลดและการอ่าน
Andrew Larsson

คำตอบ:


14

สิ่งที่ฉันไม่ชัดเจนเป็นพิเศษคือเหตุผลที่คุณจะต้องคืนค่าการรวมของคุณจาก Event Store เอง

เพราะ "เหตุการณ์" เป็นหนังสือบันทึก

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

ใช่; บางครั้งเป็นการเพิ่มประสิทธิภาพการทำงานที่มีประโยชน์ในการใช้สำเนาแคชของสถานะรวมแทนที่จะสร้างสถานะนั้นใหม่ตั้งแต่เริ่มต้นทุกครั้ง ข้อควรจำ: กฎการเพิ่มประสิทธิภาพข้อแรกคือ "ไม่" มันเพิ่มความซับซ้อนเป็นพิเศษให้กับโซลูชันและคุณต้องการหลีกเลี่ยงปัญหานั้นจนกว่าคุณจะมีแรงจูงใจทางธุรกิจที่น่าสนใจ

หากเป็นกรณีนี้ Event Store จะมีประโยชน์เฉพาะเมื่อสร้างฐานข้อมูล "เขียน" ของคุณใหม่เนื่องจากการเปลี่ยนแปลงสคีมาหรือไม่ หรือฉันกำลังพลาดอะไรที่ใหญ่กว่านี้?

คุณกำลังพลาดสิ่งที่ใหญ่กว่า

จุดแรกคือว่าถ้าคุณกำลังพิจารณาการแก้ปัญหาเหตุการณ์ที่มาก็เป็นเพราะคุณคาดหวังว่าจะมีความคุ้มค่าในการรักษาประวัติศาสตร์ของสิ่งที่เกิดขึ้นซึ่งเป็นที่จะบอกว่าคุณต้องการที่จะทำให้การเปลี่ยนแปลงไม่ทำลาย

นั่นคือเหตุผลที่เราเขียนถึงร้านค้ากิจกรรมเลย

โดยเฉพาะอย่างยิ่งนี่หมายความว่าการเปลี่ยนแปลงทุกอย่างจะต้องเขียนลงในที่จัดเก็บกิจกรรม

นักเขียนที่แข่งขันกันอาจทำลายการเขียนของกันและกันหรือผลักดันระบบให้อยู่ในสถานะที่ไม่ได้ตั้งใจหากพวกเขาไม่ได้ตระหนักถึงการแก้ไขของกันและกัน ดังนั้นวิธีการปกติเมื่อคุณต้องการความสอดคล้องคือการเขียนของคุณไปยังตำแหน่งเฉพาะในวารสาร (คล้ายกับ PUT ตามเงื่อนไขใน HTTP api) การเขียนที่ล้มเหลวจะบอกผู้เขียนว่าความเข้าใจในวารสารปัจจุบันของพวกเขาไม่ตรงกันและพวกเขาควรกู้คืน

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

ในเส้นทางแห่งความสุขคุณสามารถเก็บภาพรวมของการรวมไว้ในหน่วยความจำ คุณต้องติดต่อกับร้านค้าภายนอกเมื่อไม่มีสำเนาในเครื่องเท่านั้น

นอกจากนี้คุณไม่จำเป็นต้องถูกติดตามอย่างสมบูรณ์หากคุณมีสิทธิ์เข้าถึงหนังสือบันทึก

ดังนั้นวิธีการปกติ ( ถ้าใช้พื้นที่เก็บข้อมูลภาพ) คือการรักษามันถ่ายทอดสด ด้วยวิธีนี้หากคุณต้องการกู้คืนคุณสามารถทำได้โดยไม่ต้องโหลดซ้ำและเล่นซ้ำประวัติทั้งหมดของการรวม

มีหลายกรณีที่ความซับซ้อนนี้ไม่เป็นที่สนใจเนื่องจากการรวมเม็ดเล็ก ๆ ที่มีช่วงอายุที่กำหนดมักจะไม่รวบรวมเหตุการณ์เพียงพอสำหรับผลประโยชน์ที่เกินกว่าค่าใช้จ่ายในการบำรุงรักษาแคชสแนปชอต

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


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

6

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

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

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

โดยปกติระบบจะทำงานดังนี้:

1. Request to book seat #1
2. Check in the "already booked" list: the list is empty.
3. Issue a "booked seat #1" event.
4. Projection process catches the event, adds seat #1 to the "already booked" list.
5. Another request to book seat #1.
6. Check in the list: the list contains seat #1
7. Respond with an error message.

อย่างไรก็ตามจะเกิดอะไรขึ้นถ้าคำขอเข้ามาเร็วเกินไปและขั้นตอนที่ 5 เกิดขึ้นก่อนขั้นตอนที่ 4

1. Request to book seat #1
2. Check in the "already booked" list: the list is empty.
3. Issue a "booked seat #1" event.
4. Another request to book seat #1.
5. Check in the list: the list is still empty.
6. Issue another "booked seat #1" event.

ตอนนี้คุณมีสองกิจกรรมสำหรับจองที่นั่งเดียวกัน สถานะของระบบเสียหาย

เพื่อป้องกันไม่ให้เกิดเหตุการณ์เช่นนี้คุณไม่ควรตรวจสอบความถูกต้องของการอัปเดตกับการฉาย ในการตรวจสอบความถูกต้องของการอัพเดทคุณจะสร้างการรวมจาก Event Store อีกครั้งจากนั้นตรวจสอบความถูกต้องของการอัพเดท หลังจากนั้นคุณออกเหตุการณ์ แต่ใช้ตัวป้องกันการประทับเวลาเพื่อให้แน่ใจว่าไม่มีเหตุการณ์ใหม่ออกมานับตั้งแต่ที่คุณอ่านครั้งล่าสุดจากร้านค้า หากล้มเหลวคุณลองใหม่อีกครั้ง

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


ขอบคุณสำหรับคำตอบของคุณ (และขออภัยที่ใช้เวลานานในการตอบกลับ) สิ่งที่คุณพูดเกี่ยวกับการตรวจสอบความถูกต้องกับฐานข้อมูลการเขียนไม่จำเป็นต้องเป็นเรื่องจริง ดังที่ฉันพูดถึงในความคิดเห็นอื่นในตัวอย่างการติดตั้ง ES ฉันเล่นกับฉันเพื่อให้แน่ใจว่าอัปเดตฐานข้อมูลการเขียนของฉันพร้อมกัน (และเก็บ concurrencyId / timestamp) สิ่งนี้ทำให้ฉันสามารถตรวจสอบการละเมิดพร้อมกันในแง่ดีโดยไม่จำเป็นต้องพร้อมจาก EventStore ที่ได้รับการเขียนแบบซิงโครนัสเพียงอย่างเดียวไม่ได้ช่วยป้องกันความเสียหายของข้อมูล แต่ฉันก็ยังทำการเขียนแบบเข้าถึงแบบครั้งเดียว (เธรดเดี่ยว)
MetaFight

ดังนั้นฉันมีปัญหาความสอดคล้องของฉันแยกออก แม้ว่าฉันคิดว่านี่เป็นค่าใช้จ่ายในการปรับขนาดได้
MetaFight

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

1
หากการอ่านการฉายล้มเหลวมันจะลองใหม่จนกว่าจะประสบความสำเร็จ หากเกิดปัญหาขึ้นทันทีมันจะตื่นขึ้นมาและดำเนินการต่อจากจุดที่มันชน - กล่าวอีกนัยหนึ่งให้ลองอีกครั้ง ผลที่สังเกตได้จากภายนอกจะไม่แตกต่างจากมันแค่วิ่งช้า หากการคาดการณ์ยังคงล้มเหลวและล้มเหลวอย่างต่อเนื่องนั่นหมายความว่ามีข้อผิดพลาดในนั้นและจะต้องได้รับการแก้ไข หลังจากแก้ไขแล้วจะเริ่มการทำงานจากสถานะที่ดีล่าสุด หากฐานข้อมูลการอ่านทั้งหมดเสียหายเนื่องจากข้อผิดพลาดฉันจะสร้างฐานข้อมูลใหม่ตั้งแต่ต้นโดยใช้ประวัติเหตุการณ์
Fyodor Soikin

1
ไม่เคยมีข้อมูลสูญหายนั่นคือประเด็นใหญ่ ข้อมูลอาจติดอยู่ในรูปแบบที่ไม่สะดวก (สำหรับการอ่าน) ชั่วครู่หนึ่ง แต่ก็ไม่เคยสูญหาย
Fyodor Soikin

3

เหตุผลหลักคือประสิทธิภาพ คุณสามารถจัดเก็บสแน็ปช็อตสำหรับทุกการกระทำ (กระทำ = เหตุการณ์ที่สร้างขึ้นโดยคำสั่งเดียวโดยปกติจะมีเพียงหนึ่งเหตุการณ์) แต่ค่าใช้จ่ายสูง ในภาพรวมคุณจะต้องเก็บบันทึกการกระทำมิฉะนั้นจะไม่เป็นการจัดหากิจกรรม และทั้งหมดนี้ต้องทำแบบอะตอมทั้งหมดหรือเปล่า คำถามของคุณจะถูกต้องก็ต่อเมื่อมีการใช้ฐานข้อมูล / ตาราง / คอลเลกชันที่แยกจากกัน (ไม่เช่นนั้นจะเป็นการจัดหากิจกรรม) ดังนั้นคุณจึงจำเป็นต้องใช้ธุรกรรมเพื่อรับประกันความมั่นคง ธุรกรรมไม่สามารถปรับขนาดได้ สตรีมเหตุการณ์ต่อท้ายเท่านั้น (Event store) เป็นแม่ของความสามารถในการขยาย

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


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

ปัญหากำลังตรวจจับการเปลี่ยนแปลงนั้น การรวมอาจมีขนาดใหญ่มากโดยมีไฟล์ / คลาสจำนวนมาก
Constantin Galbenu

ฉันไม่เข้าใจ การเปลี่ยนแปลงจะเกิดขึ้นกับการเปิดตัวซอฟต์แวร์ การเปิดตัวอาจมาพร้อมกับสคริปต์ฐานข้อมูลเพื่อสร้างฐานข้อมูล "เขียน" ใหม่
MetaFight

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

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