การจัดหากิจกรรมหนึ่งเหตุการณ์สถานะของการรวมสองเหตุการณ์เปลี่ยนไป


10

ฉันพยายามเรียนรู้วิธีการของ DDD และวิชาที่เกี่ยวข้อง ฉันมาพร้อมกับความคิดของบริบทที่ล้อมรอบง่าย ๆ ที่จะใช้ "ธนาคาร": มีบัญชีเงินสามารถฝากถอนและโอนระหว่างพวกเขา นอกจากนี้ยังเป็นสิ่งสำคัญในการเก็บประวัติการเปลี่ยนแปลง

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

เมื่อพิจารณาการฝากและถอน - มันค่อนข้างง่ายเพราะมีเพียงหนึ่งรวมที่แก้ไข

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

คำถามของฉันคือ - ฉันจะนำหลักการทั้งสามเหล่านั้นมารวมกันได้อย่างไร: "หนึ่งรายการรวมหนึ่งธุรกรรม", "เหตุการณ์ -> การเปลี่ยนแปลงโดยรวม" และ "การป้องกันการแก้ไขพร้อมกัน"

คำตอบ:


7

เมื่อโอนเงินจะแตกต่างกัน - การรวมสองรายการจะต้องแก้ไขโดยเหตุการณ์ MoneyTransferred หนึ่งเหตุการณ์

การโอนเงินเป็นการกระทำที่แยกต่างหากจากการอัปเดตบัญชีแยกประเภท

MoneyTransferred
AccountCredited
AccountDebited

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

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

แน่นอนคำตอบนั้นอยู่ที่นั่นในภาษาที่แพร่หลาย - บัญชีได้รับเครดิตหรือหักบัญชีเพื่อสะท้อนภาระหน้าที่ของธนาคารที่มีต่อลูกค้า

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

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

สิ่งที่สอง - มันหมายความว่าฉันต้องใช้บางอย่างเช่นเทพนิยาย

การสะกดคำที่แตกต่างกันเล็กน้อย: คุณต้องการสิ่งที่เหมือนมนุษย์เยี่ยงอย่างคำสั่งที่ถูกต้อง

มีอย่างน้อยสองวิธีที่คุณสามารถทำได้ หนึ่งจะต้องมีสมาชิกฟังMoneyTransferredและส่งคำสั่งทั้งสองไปยังบัญชีแยกประเภท

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


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

ไม่ได้ -> AccountCreditedและAccoundDebitedแล้วMoneyTransferred ? วิธีแก้ปัญหาแรกอัปเดตมวลรวมในหนึ่งธุรกรรม (ไม่มีการรับประกันความสอดคล้องใด ๆ ) นอกจากนี้ยังไม่มีการรวมที่สามารถเผยแพร่MoneyTransferred -> ไม่มีความสัมพันธ์ ทางออกที่สองน่าจะดีกว่า - ProcessTransactionสามารถเผยแพร่MoneyTransferredและเพื่อหลีกเลี่ยงการแก้ไขรวมหลายรายการในหนึ่งธุรกรรมฉันสามารถเผยแพร่กิจกรรมจากบัญชีหลังจากทำธุรกรรม ขอโทษที่เป็นจู้จี้จุกจิก เป็นการยากที่จะเข้าใจสำหรับผู้เริ่มต้น - ไม่สามารถใช้รูปแบบเดียวเท่านั้นที่ไม่มีรูปแบบอื่น ๆ
cocsackie

1

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

แบริ่งในใจการกระทำของการโอนเงินที่ไม่ควรจะมีการปรับปรุงแต่เพื่อแทรกลงในaccounttransaction

ที่ถูกกล่าวว่ามีอีกกฎที่สำคัญ: การกระทำของการเพิ่มที่transactionควรจะเป็นอะตอมที่มีการปรับปรุงการ (เขตสมดุลของ accountdenormalized)

ตอนนี้ถ้าฉันเข้าใจแนวคิด DDD ของการรวมกันสิ่งต่อไปนี้ดูเหมือนจะเกี่ยวข้อง:

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

ดังนั้นในแง่ของการออกแบบ DDD ฉันอยากจะแนะนำ:

  1. มีการรวมหนึ่งรายการเพื่อแสดงการถ่ายโอน

  2. ผลรวมประกอบด้วยวัตถุต่อไปนี้: การถ่ายโอน (วัตถุราก); วัตถุรากเชื่อมโยงกับสองรายการธุรกรรม (หนึ่งรายการสำหรับแต่ละบัญชี); และแต่ละรายการธุรกรรมจะเชื่อมโยงกับหนึ่งบัญชี

  3. การเข้าถึงการถ่ายโอนทั้งหมดควรได้รับการทำสมาธิโดยวัตถุราก ( transfer)

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

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

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

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


0

ฉันยังอยู่ในขั้นตอนการเรียนรู้ จากมุมมองของการนำไปใช้นี่คือสิ่งที่ฉันรู้สึกว่าคุณจะต้องดำเนินการนี้

ส่ง TransferMoneyCommand ที่เพิ่มกิจกรรมต่อไปนี้ [MoneyTransferEvent, AccountDebitedEvent]

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

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

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

AccountDebitedEvent จะลบเงินออกจากบัญชีของผู้ชำระเงิน (อัปเดตสถานะรวมและโมเดลมุมมอง / การฉายใด ๆ ที่เกี่ยวข้อง)

MoneyTransferEvent เริ่ม Saga / ผู้จัดการกระบวนการ

หน้าที่ของผู้จัดการซะงะ / กระบวนการจะพยายามเครดิตบัญชีของผู้รับเงินหากล้มเหลวก็จะต้องเครดิตยอดคงเหลือกลับไปยังผู้จ่าย

ผู้จัดการ Saga / กระบวนการจะเผยแพร่ CreditAccountCommand ซึ่งจะนำไปใช้กับบัญชีของผู้รับเงินและหากประสบความสำเร็จมากกว่า AccountCreditedEvent

จากมุมมองการจัดหากิจกรรมหากคุณต้องการย้อนกลับการกระทำนี้กิจกรรมทั้งหมดในธุรกรรมนี้จะมีรหัสสหสัมพันธ์ / รหัสสาเหตุเป็น TransferMoneyCommand ต้นฉบับซึ่งคุณสามารถใช้เพื่อเพิ่มกิจกรรมสำหรับการดำเนินการยกเลิก / กลับรายการ

อย่าลังเลที่จะแนะนำปัญหาหรือการปรับปรุงที่อาจเกิดขึ้นข้างต้น

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