การจัดการภาวะพร้อมกัน ES / CQRS


20

ฉันเพิ่งเริ่มดำน้ำใน CQRS / ES เพราะฉันอาจจำเป็นต้องใช้มันในที่ทำงาน ดูเหมือนว่าจะมีแนวโน้มมากในกรณีของเราเพราะมันจะแก้ปัญหาได้มากมาย

ฉันร่างความเข้าใจคร่าวๆว่าแอพ ES / CQRS ควรมีลักษณะเป็นบริบทอย่างไรในกรณีการใช้งานธนาคารที่เรียบง่าย (ถอนเงิน)

ES / CQRS

เพื่อสรุปหากบุคคล A ถอนเงิน:

  • ออกคำสั่ง
  • คำสั่งถูกมอบให้สำหรับการตรวจสอบ / การตรวจสอบ
  • เหตุการณ์ถูกส่งไปยังที่เก็บเหตุการณ์หากการตรวจสอบความถูกต้องสำเร็จ
  • ผู้รวบรวมจะ dequeues เหตุการณ์เพื่อใช้การแก้ไขกับการรวม

จากสิ่งที่ฉันเข้าใจบันทึกเหตุการณ์เป็นที่มาของความจริงเนื่องจากมันเป็นบันทึกของข้อเท็จจริงเราก็จะสามารถประมาณการภาพใด ๆ ออกมาได้


ตอนนี้สิ่งที่ฉันไม่เข้าใจในสิ่งที่ยิ่งใหญ่นี้คือสิ่งที่เกิดขึ้นในกรณีนี้:

  • กฎ: ยอดคงเหลือต้องไม่ติดลบ
  • person A มียอดคงเหลือ 100e
  • person A ทำการถอนคำสั่งจาก 100e
  • ผ่านการตรวจสอบและ MoneyWithdrewEvent 100e เหตุการณ์
  • ในระหว่างนี้บุคคล A ออกการถอนคำสั่งอีก 100e
  • MoneyWithdrewEvent ตัวแรกไม่ได้รับการรวบรวม แต่ยังผ่านการตรวจสอบเนื่องจากการตรวจสอบการตรวจสอบกับการรวม (ที่ยังไม่ได้รับการปรับปรุง)
  • MoneyWithdrewEvent 100e ถูกปล่อยออกมาอีกครั้ง

==> เราอยู่ในสถานะที่ไม่สอดคล้องกันของยอดคงเหลือที่ -100e และบันทึกมี 2 MoneyWithdrewEvent

ที่ฉันเข้าใจมีหลายกลยุทธ์ที่จะรับมือกับปัญหานี้:

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

คำถามที่เกี่ยวข้องกับกลยุทธ์:

  • ก) ในกรณีนี้บันทึกเหตุการณ์ไม่ใช่ที่มาของความจริงอีกต่อไปจะจัดการกับมันอย่างไร นอกจากนี้เรากลับไปที่ลูกค้าตกลงในขณะที่มันผิดทั้งหมดเพื่อให้ถอนได้ดีกว่าในกรณีนี้ที่จะใช้ล็อค?
  • b) ล็อค == การหยุดชะงักคุณมีข้อมูลเชิงลึกเกี่ยวกับแนวปฏิบัติที่ดีที่สุดหรือไม่?

โดยรวมแล้วความเข้าใจของฉันถูกต้องเกี่ยวกับวิธีจัดการกับการเกิดพร้อมกันหรือไม่

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


ทำไมไม่อัปเดตการรวมในขั้นตอนที่ 4 แทนที่จะรอจนถึงขั้นตอนที่ 7
Erik Eidt

ดังนั้นคุณหมายความว่าในกรณีนี้ที่เก็บเหตุการณ์เป็นเพียงบันทึกที่ได้รับการอ่านเมื่อเริ่มต้นแอปพลิเคชันเพื่อสร้างการรวม / การฉายอื่น ๆ เท่านั้น
Louis F.

คำตอบ:


19

ฉันร่างความเข้าใจคร่าวๆว่าแอพ ES / CQRS ควรมีลักษณะเป็นบริบทอย่างไรในกรณีการใช้งานธนาคารที่เรียบง่าย (ถอนเงิน)

นี่คือตัวอย่างที่สมบูรณ์แบบของแอปพลิเคชันที่มาจากเหตุการณ์ เริ่มกันเลย.

ทุกครั้งที่มีการประมวลผลคำสั่งหรือลองใหม่ (คุณจะเข้าใจมีความอดทน) ทำตามขั้นตอนต่อไปนี้:

  1. Application layerคำสั่งถึงจัดการคำสั่งเช่นการให้บริการในการ
  2. ตัวจัดการคำสั่งจะระบุAggregateและโหลดจากที่เก็บ (ในกรณีนี้การโหลดจะดำเนินการโดยnew-ing Aggregateอินสแตนซ์, ดึงเหตุการณ์ทั้งหมดที่ปล่อยออกมาก่อนหน้านี้ของการรวมนี้และนำไปใช้กับ Aggregate อีกครั้ง; ใช้ในภายหลัง; หลังจากเหตุการณ์ถูกนำไปใช้ผลรวมจะอยู่ในสถานะสุดท้าย - นั่นคือยอดเงินในบัญชีปัจจุบันจะถูกคำนวณเป็นตัวเลข)
  3. ตัวจัดการคำสั่งเรียกเมธอดที่เหมาะสมบนAggregate, like Account::withdrawMoney(100)และรวบรวมเหตุการณ์ที่ให้ผลลัพธ์เช่นMoneyWithdrewEvent(AccountId, 100); หากมีเงินไม่เพียงพอในบัญชี (ยอดคงเหลือ <100) การยกเว้นจะถูกยกขึ้นและทั้งหมดจะถูกยกเลิก มิฉะนั้นขั้นตอนต่อไปจะดำเนินการ
  4. ตัวจัดการคำสั่งพยายามคงอยู่ในAggregateที่เก็บข้อมูล (ในกรณีนี้ที่เก็บคือEvent Store); มันทำเช่นนั้นโดยการผนวกเหตุการณ์ใหม่เข้ากับEvent streamif และเฉพาะถ้าversionของAggregateยังคงเป็นเหตุการณ์ที่เกิดขึ้นเมื่อAggregateถูกโหลด ถ้ารุ่นคือไม่เหมือนกันแล้วคำสั่งจะถูกลอง - ไปที่ขั้นตอนที่ 1 หากversionเหมือนกันเหตุการณ์จะถูกผนวกเข้ากับEvent streamและลูกค้าจะได้รับSuccessสถานะ

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

คำEvent streamนี้เป็นสิ่งที่เป็นนามธรรมรอบเหตุการณ์ทั้งหมดที่ถูกปล่อยออกมาโดยกลุ่มคนเดียวกัน

คุณควรเข้าใจว่าการEvent storeคงอยู่เป็นอีกรูปแบบหนึ่งที่เก็บการเปลี่ยนแปลงทั้งหมดไว้ในการรวมไม่ใช่เพียงแค่สถานะสุดท้าย

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

ที่เก็บเหตุการณ์เป็นที่มาของความจริงเสมอ

b) ล็อค == การหยุดชะงักคุณมีข้อมูลเชิงลึกเกี่ยวกับแนวปฏิบัติที่ดีที่สุดหรือไม่?

โดยใช้การล็อคในแง่ดีคุณไม่มีล็อคเพียงแค่ลองใหม่อีกครั้ง

Anyways, Locks! = deadlocks


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

ตกลงฉันคิดว่าความสับสนของฉันมาจากความจริงที่ว่า event store == event bus (ฉันมี kafka อยู่ในใจ) ดังนั้นการสร้างใหม่รวมอาจมีค่าใช้จ่ายเนื่องจากคุณอาจจำเป็นต้องอ่านเหตุการณ์จำนวนมาก ในกรณีที่มีสแนปชอตของAggregateควรจะอัปเดตสแน็ปช็อตเมื่อใด ที่เก็บสแน็ปช็อตเหมือนกับที่จัดเก็บกิจกรรมหรือเป็นมุมมองที่เป็นรูปธรรมซึ่งได้มาจาก Event Bus หรือไม่
Louis F.

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

ตกลงฉันคิดว่าฉันมีวิสัยทัศน์ที่ชัดเจนเกี่ยวกับวิธีจัดการกับเรื่องนี้ ตอนนี้คำถามสุดท้ายบทบาทของบัสเหตุการณ์ในท้ายที่สุดคืออะไร? หากมีการอัพเดทรวมกัน?
Louis F.

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

1

ฉันร่างความเข้าใจคร่าวๆว่าแอพ ES / CQRS ควรมีลักษณะเป็นบริบทอย่างไรในกรณีการใช้งานธนาคารที่เรียบง่าย (ถอนเงิน)

ปิด. ปัญหาคือว่าตรรกะสำหรับการอัปเดต "รวม" ของคุณอยู่ในสถานที่แปลก

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

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

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

กล่าวอีกนัยหนึ่งลูกศร 2 & 3 (ถ้ามี) โดยปกติจะเชื่อมต่อกับที่จัดเก็บกิจกรรมไม่ใช่ไปที่ร้านรวม

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

รูปแบบนี้เป็นกรณีปกติ - มากกว่าท้ายไปยังสตรีมในกระแสเหตุการณ์ที่เรามักจะใส่ไปยังสถานที่เฉพาะในสตรีม; หากการดำเนินการนั้นไม่สอดคล้องกับสถานะของการจัดเก็บการเขียนล้มเหลวและบริการสามารถเลือกโหมดความล้มเหลวที่เหมาะสม (ไม่สามารถไคลเอนต์ลองอีกครั้งรวม ... ) การใช้ idempotent write ช่วยแก้ปัญหาต่าง ๆ ในการส่งข้อความแบบกระจาย แต่แน่นอนว่ามันต้องมีร้านค้าที่รองรับการเขียน idempotent


อืมฉันคิดว่าฉันเข้าใจองค์ประกอบของที่จัดเก็บเหตุการณ์ผิดพลาด ฉันคิดว่าทุกสิ่งควรผ่านและได้รับการสตรีม ถ้าร้านค้ากิจกรรมของฉันเป็นคาฟคาและอ่านได้อย่างเดียว? ฉันไม่สามารถจ่ายได้ในขั้นตอนที่ 2 และ 3 เพื่ออ่านข้อความทั้งหมดอีกครั้ง ดูเหมือนว่าโดยรวมแล้ววิสัยทัศน์ของฉันตรงกับสิ่งนี้: medium.com/technology-learning/…
Louis F.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.