DDD - กฎที่เอนทิตีไม่สามารถเข้าถึงที่เก็บโดยตรง


185

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

สิ่งนี้มาจากหนังสือของ Eric Evans Domain Driven Designหรือมาจากที่อื่นหรือไม่

มีคำอธิบายที่ดีสำหรับเหตุผลอยู่เบื้องหลังที่ไหน?

แก้ไข: เพื่อชี้แจง: ฉันไม่ได้พูดถึงวิธีปฏิบัติแบบ OO แบบคลาสสิกของการแยกการเข้าถึงข้อมูลออกเป็นเลเยอร์แยกต่างหากจากตรรกะทางธุรกิจ - ฉันกำลังพูดถึงการจัดเรียงเฉพาะโดยที่ DDD หน่วยงานไม่ควรพูดคุยกับข้อมูล access layer เลย (เช่นพวกเขาไม่ควรจะเก็บการอ้างอิงไปยังวัตถุ Repository)

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

อัปเดต: มีนาคม 2556 จำนวนผู้โหวตในคำถามบ่งบอกว่ามีความสนใจในเรื่องนี้มากมายและถึงแม้ว่าจะมีคำตอบมากมาย แต่ฉันก็ยังคิดว่าจะมีช่องว่างมากขึ้นถ้าผู้คนมีความคิดเกี่ยวกับเรื่องนี้


ลองดูที่คำถามของฉันstackoverflow.com/q/8269784/235715มันแสดงสถานการณ์เมื่อมันยากที่จะจับตรรกะโดยที่องค์กรไม่สามารถเข้าถึงที่เก็บได้ แม้ว่าฉันคิดว่าเอนทิตีไม่ควรเข้าถึงที่เก็บข้อมูลและมีวิธีแก้ไขสถานการณ์ของฉันเมื่อรหัสสามารถเขียนใหม่ได้โดยไม่ต้องอ้างอิงที่เก็บข้อมูล แต่ในปัจจุบันฉันไม่สามารถคิดได้
Alex Burtsev

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

คำตอบ:


47

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

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

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

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

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

แต่เรามาพูดถึงกฎการตรวจสอบที่ซับซ้อนมากขึ้น สมมติว่าคุณเป็น Amazon.com คุณเคยสั่งซื้อสินค้าด้วยบัตรเครดิตที่หมดอายุหรือไม่? ฉันมีที่ฉันยังไม่ได้อัปเดตการ์ดและซื้ออะไร ยอมรับการสั่งซื้อและ UI แจ้งให้ฉันทราบว่าทุกอย่างเป็นสีพีช ประมาณ 15 นาทีต่อมาฉันจะได้รับอีเมลแจ้งว่ามีปัญหากับการสั่งซื้อบัตรเครดิตของฉันไม่ถูกต้อง สิ่งที่เกิดขึ้นที่นี่ก็คือมีการตรวจสอบ regex ในเลเยอร์โดเมน นี่เป็นหมายเลขบัตรเครดิตที่ถูกต้องหรือไม่ ถ้าใช่ยังคงมีอยู่เพื่อ อย่างไรก็ตามมีการตรวจสอบเพิ่มเติมที่ชั้นงานของแอปพลิเคชันซึ่งมีการสอบถามบริการภายนอกเพื่อดูว่าสามารถชำระเงินด้วยบัตรเครดิตได้หรือไม่ ถ้าไม่จริงไม่ส่งอะไรระงับคำสั่งซื้อและรอลูกค้า

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


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

การตอบกลับของฉันใหญ่กว่าช่องแสดงความคิดเห็นที่จะอนุญาตให้ดูการแก้ไข
kertosis

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

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

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

35

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

  1. เราควรทราบเจตนาของเราในคำขอและสิ่งที่เราต้องการจากโดเมนดังนั้นเราสามารถทำการเรียกที่เก็บข้อมูลก่อนที่จะสร้างหรือเรียกใช้พฤติกรรมการรวม นอกจากนี้ยังช่วยหลีกเลี่ยงปัญหาสถานะในหน่วยความจำที่ไม่สอดคล้องกันและความจำเป็นในการโหลดแบบสันหลังยาว (ดูบทความนี้ ) กลิ่นคือคุณไม่สามารถสร้างอินสแตนซ์ในหน่วยความจำของเอนทิตี้ของคุณได้อีกต่อไปโดยไม่ต้องกังวลเกี่ยวกับการเข้าถึงข้อมูล
  2. CQS (Command Query Separation) สามารถช่วยลดความต้องการที่จะเรียกพื้นที่เก็บข้อมูลสำหรับสิ่งต่าง ๆ ในเอนทิตีของเรา
  3. เราสามารถใช้สเปคเพื่อห่อหุ้มและสื่อสารความต้องการทางตรรกะของโดเมนและส่งผ่านไปยังที่เก็บแทน (บริการสามารถประสานสิ่งเหล่านี้ให้เราได้) ข้อมูลจำเพาะอาจมาจากเอนทิตีที่รับผิดชอบการบำรุงรักษาค่าคงที่ พื้นที่เก็บข้อมูลจะตีความบางส่วนของข้อมูลจำเพาะลงในการใช้งานแบบสอบถามของตัวเองและใช้กฎจากข้อกำหนดในผลลัพธ์แบบสอบถาม สิ่งนี้มีวัตถุประสงค์เพื่อให้ตรรกะของโดเมนในเลเยอร์โดเมน นอกจากนี้ยังให้บริการภาษาแพร่หลายและการสื่อสารที่ดีขึ้น ลองนึกภาพว่า "ข้อกำหนดการสั่งซื้อที่ค้างชำระ" กับการพูดว่า "คำสั่งซื้อตัวกรองจาก tbl_order โดยที่ paste_at น้อยกว่า 30 นาทีก่อน sysdate" (ดูคำตอบนี้)
  4. มันทำให้การให้เหตุผลเกี่ยวกับพฤติกรรมของหน่วยงานที่ยากขึ้นเนื่องจากมีการละเมิดหลักการความรับผิดชอบเดี่ยว หากคุณต้องการแก้ไขปัญหาพื้นที่เก็บข้อมูล / การคงอยู่คุณจะรู้ว่าจะไปที่ไหนและไปที่ไหน
  5. มันหลีกเลี่ยงอันตรายจากการให้เอนทิตีแบบสองทิศทางเข้าถึงรัฐทั่วโลก (ผ่านพื้นที่เก็บข้อมูลและบริการโดเมน) คุณไม่ต้องการทำลายขอบเขตการทำธุรกรรมของคุณ

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

ตามกฎของหัวแม่มือเราควรพยายามหลีกเลี่ยงการใช้ Repositories (12) จากภายใน Aggregates ถ้าเป็นไปได้

เวอร์นอน, วอน (2013-02-06) การใช้การออกแบบที่ขับเคลื่อนด้วยโดเมน (ที่ตั้งจุด 6089) การศึกษาของเพียร์สัน จุด Edition.

และในบทที่ 10 เกี่ยวกับการรวมในส่วนที่ชื่อว่า "การนำทางแบบจำลอง"เขากล่าว (หลังจากที่เขาแนะนำให้ใช้รหัสเฉพาะทั่วโลกสำหรับการอ้างอิงรากรวมอื่น ๆ ):

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

เขาไปสู่แสดงตัวอย่างของนี้ในรหัส:

public class ProductBacklogItemService ... { 

   ... 
   @Transactional 
   public void assignTeamMemberToTask( 
        String aTenantId, 
        String aBacklogItemId, 
        String aTaskId, 
        String aTeamMemberId) { 

        BacklogItem backlogItem = backlogItemRepository.backlogItemOfId( 
                                        new TenantId( aTenantId), 
                                        new BacklogItemId( aBacklogItemId)); 

        Team ofTeam = teamRepository.teamOfId( 
                                  backlogItem.tenantId(), 
                                  backlogItem.teamId());

        backlogItem.assignTeamMemberToTask( 
                  new TeamMemberId( aTeamMemberId), 
                  ofTeam,
                  new TaskId( aTaskId));
   } 
   ...
}     

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

จากนั้นฉันได้พูดคุยกับ Marco Pivetta @Ocramiusผู้ใจดีที่แสดงรหัสให้ฉันเล็กน้อยเพื่อดึงข้อมูลจำเพาะจากโดเมนและใช้สิ่งนั้น:

1) ไม่แนะนำ:

$user->mountFriends(); // <-- has a repository call inside that loads friends? 

2) ในบริการโดเมนนี่เป็นสิ่งที่ดี:

public function mountYourFriends(MountFriendsCommand $mount) { /* see http://store.steampowered.com/app/296470/ */ 
    $user = $this->users->get($mount->userId()); 
    $friends = $this->users->findBySpecification($user->getFriendsSpecification()); 
    array_map([$user, 'mount'], $friends); 
}

1
คำถาม: เราถูกสอนเสมอว่าอย่าสร้างวัตถุในสถานะที่ไม่ถูกต้องหรือไม่สอดคล้องกัน เมื่อคุณโหลดผู้ใช้จากที่เก็บและจากนั้นคุณโทรgetFriends()ก่อนที่จะทำสิ่งอื่นใดมันจะว่างเปล่าหรือถูกโหลด หากไม่มีข้อมูลแสดงว่าวัตถุนี้อยู่ในสถานะไม่ถูกต้อง ความคิดใด ๆ เกี่ยวกับเรื่องนี้?
Jimbo

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

28

เป็นคำถามที่ดีมาก ฉันจะรอการสนทนาเกี่ยวกับเรื่องนี้ แต่ฉันคิดว่ามันถูกกล่าวถึงในหนังสือ DDD หลายเล่มและ Jimmy nilssons และ Eric Evans ฉันเดาว่ามันสามารถมองเห็นได้ผ่านตัวอย่างวิธีใช้รูปแบบ reposistory

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

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

ฉันเดา (โดยไม่มีตัวอย่าง) การทดสอบหน่วยจะซับซ้อนกว่า

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


1
จุดดี. แบบจำลองโดเมนโรงเรียนเก่าน่าจะมี Entity B ที่รับผิดชอบในการตรวจสอบตัวเองก่อนที่มันจะช่วยให้ตัวเองได้รับการยืนยันฉันเดา คุณแน่ใจหรือว่า Evans กล่าวถึงเอนทิตีที่ไม่ได้ใช้ที่เก็บ? ฉันผ่านหนังสือไปครึ่งทางแล้วยังไม่ได้เอ่ยถึง ...
codeulike

ฉันอ่านหนังสือเมื่อหลายปีก่อน (3 ดี ... ) และความทรงจำของฉันก็ล้มเหลว ฉันจำไม่ได้ว่าเขาพูดจริงหรือไม่ แต่ฉันเชื่อว่าเขาแสดงตัวอย่างนี้ผ่านตัวอย่าง นอกจากนี้คุณยังสามารถค้นหาความหมายของชุมชนสินค้าตัวอย่างของเขา (จากหนังสือของเขา) ที่dddsamplenet.codeplex.com ดาวน์โหลดโปรเจ็กต์โค้ด (ดูที่โครงการวานิลลา - ตัวอย่างจากหนังสือ) คุณจะพบว่าที่เก็บจะใช้ใน Application layer สำหรับการเข้าถึงเอนทิตีโดเมนเท่านั้น
แมกนัส Backeus

1
กำลังดาวน์โหลดตัวอย่าง DDD SmartCA จากหนังสือ p2p.wrox.com/คุณจะเห็นวิธีการอื่น (แม้ว่านี่จะเป็นไคลเอนต์ Windows RIA) ที่ใช้ที่เก็บในบริการ (ไม่มีอะไรแปลกที่นี่) แต่มีการใช้บริการภายในสิทธิ์ นี่คือสิ่งที่ฉันจะไม่ทำ แต่ฉันเป็นคนที่แต่งตัวประหลาดแอปเว็บบ์ เมื่อพิจารณาสถานการณ์ของแอป SmartCA ที่คุณต้องทำงานแบบออฟไลน์ได้การออกแบบ ddd อาจดูแตกต่างออกไป
แมกนัส Backeus

ตัวอย่าง SmartCA ฟังดูน่าสนใจซึ่งมีบทใดบ้าง (การดาวน์โหลดรหัสจะถูกจัดเรียงตามบท)
codeulike

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

13

ทำไมต้องแยกการเข้าถึงข้อมูลออก

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

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

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

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

ในอีกทางหนึ่งนักพัฒนาที่แนะนำข้อกังวลทางเทคนิคมากเกินไปในรูปแบบโดเมนอาจทำให้เกิดการแบ่งเช่นกัน

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

พิเศษ:

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

วิธีที่ฉันตีความสิ่งนี้ถ้าคุณลงเอยด้วยรหัสของบรรทัดที่เกี่ยวข้องกับสิ่งต่าง ๆ เช่นการเข้าถึงฐานข้อมูลคุณสูญเสียการสื่อสารที่

หากความต้องการในการเข้าถึงฐานข้อมูลมีไว้สำหรับตรวจสอบความเป็นเอกลักษณ์ให้ดูที่:

Udi Dahan: ทีมผิดพลาดที่ใหญ่ที่สุดเกิดขึ้นเมื่อใช้ DDD

http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-applying-ddd/

ภายใต้ "กฎทั้งหมดไม่ได้สร้างขึ้นเท่ากัน"

และ

ใช้รูปแบบโดเมน

http://msdn.microsoft.com/en-us/magazine/ee236415.aspx#id0400119

ภายใต้ "สถานการณ์จำลองสำหรับการไม่ใช้โมเดลโดเมน" ซึ่งสัมผัสกับหัวเรื่องเดียวกัน

วิธีแยกการเข้าถึงข้อมูล

กำลังโหลดข้อมูลผ่านอินเทอร์เฟซ

"data access layer" ได้รับการสรุปผ่านส่วนต่อประสานที่คุณเรียกใช้เพื่อดึงข้อมูลที่ต้องการ:

var orderLines = OrderRepository.GetOrderLines(orderId);

foreach (var line in orderLines)
{
     total += line.Price;
}

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

จุดด้อย: รหัสการโทรจะต้องถือว่าสิ่งที่ถูกโหลดและสิ่งที่ไม่ได้

สมมติว่า GetOrderLines ส่งคืนออบเจกต์ OrderLine พร้อมคุณสมบัติ null ProductInfo สำหรับเหตุผลด้านประสิทธิภาพ ผู้พัฒนาจะต้องมีความรู้เกี่ยวกับโค้ดหลังอินเทอร์เฟซ

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

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

สรุป: การแยกต่ำค่อนข้างยุติธรรม!

ขี้เกียจโหลด

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

foreach (var line in order.OrderLines)
{
    total += line.Price;
}

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

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

บทบาทอินเทอร์เฟซ / การดึงข้อมูลกระตือรือร้น

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

กลยุทธ์การดึงข้อมูลอาจมีลักษณะเช่นนี้:

public class BillOrderFetchingStrategy : ILoadDataFor<IBillOrder, Order>
{
    Order Load(string aggregateId)
    {
        var order = new Order();

        order.Data = GetOrderLinesWithPrice(aggregateId);
    
        return order;
    }

}
   

จากนั้นการรวมของคุณจะมีลักษณะดังนี้:

public class Order : IBillOrder
{
    void BillOrder(BillOrderCommand command)
    {
        foreach (var line in this.Data.OrderLines)
        {
            total += line.Price;
        }

        etc...
    }
}

BillOrderFetchingStrategy ใช้เพื่อสร้างผลรวมแล้วผลรวมจะทำงานได้

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

ข้อด้อย: ผู้พัฒนายังคงต้องปรับ / ตรวจสอบกลยุทธ์การดึงข้อมูลหลังจากเปลี่ยนรหัสโดเมน

ด้วยวิธีการดึงข้อมูลกลยุทธ์คุณยังอาจพบว่าคุณกำลังเปลี่ยนรหัสการดึงข้อมูลที่กำหนดเองสำหรับการเปลี่ยนแปลงในกฎเกณฑ์ทางธุรกิจ มันไม่ใช่การแยกข้อกังวลอย่างสมบูรณ์แบบ แต่จะจบลงด้วยการบำรุงรักษาได้ดีกว่าตัวเลือกแรก กลยุทธ์การดึงข้อมูลจะสรุปข้อมูล HOW, WHEN และ WHERE มันมีการแยกข้อกังวลที่ดีกว่าโดยไม่สูญเสียความยืดหยุ่นเหมือนขนาดเดียวที่เหมาะกับวิธีการโหลดแบบขี้เกียจทั้งหมด


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

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

1
ไม่แน่ใจว่าจะแยกความคิดเห็นล่าสุดของคุณอย่างไร แต่ฉันคิดว่าคุณแนะนำว่าไม่ควรโหลดข้อมูลขณะประมวลผลกฎทางธุรกิจ ฉันเห็นว่าจะทำให้กฎ 'บริสุทธิ์' แต่กฎทางธุรกิจหลายประเภทจะต้องอ้างถึงข้อมูลอื่น ๆ - คุณแนะนำว่าควรจะโหลดวัตถุแยกต่างหากล่วงหน้าหรือไม่
codeulike

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

12

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

http://thinkbeforecoding.com/post/2009/03/04/How-not-to-inject-services-in-entities


ใช่บล็อกนั้นเกี่ยวกับปัญหาที่แน่นอนที่ฉันสนใจขอบคุณ
codeulike

12

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

ดังนั้น (ที่มีความเสี่ยงในการเขียนสิ่งที่ฉันไม่เห็นด้วยกับหนึ่งปีนับจากนี้) นี่คือการค้นพบของฉัน

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

// Entity
public class Invoice
{
    ...
    public void SetStatus(StatusCode statusCode, DateTime dateTime) { ... }
    public void CreateCreditNote(decimal amount) { ... }
    ...
}

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

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

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

public class Invoice
{
    ...

    // Simple method injection
    public void SetStatus(IInvoiceLogger logger, StatusCode statusCode, DateTime dateTime)
    { ... }

    // Double dispatch
    public void CreateCreditNote(ICreditNoteService creditNoteService, decimal amount)
    {
        creditNoteService.CreateCreditNote(this, amount);
    }

    ...
}

CreateCreditNote()ตอนนี้ต้องใช้บริการที่รับผิดชอบในการสร้างบันทึกเครดิต จะใช้การจัดส่งสองครั้งอย่างเต็มที่ถ่ายงานไปยังบริการที่รับผิดชอบในขณะที่ยังคงสามารถค้นพบได้จากInvoiceนิติบุคคล

SetStatus()ตอนนี้มีการพึ่งพาง่ายบนตัดไม้ซึ่งเห็นได้ชัดว่าจะดำเนินการเป็นส่วนหนึ่งของการทำงาน

IInvoiceServiceสำหรับหลังจะทำให้สิ่งที่ง่ายขึ้นในรหัสลูกค้าเราแทนอาจเข้าสู่ระบบผ่าน ท้ายที่สุดแล้วการบันทึกใบแจ้งหนี้ก็ดูเหมือนว่าจะมีอยู่ในใบแจ้งหนี้ ซิงเกิ้ลดังกล่าวIInvoiceServiceช่วยหลีกเลี่ยงความต้องการบริการมินิทุกประเภทสำหรับการดำเนินการต่างๆ ข้อเสียคือมันจะกลายเป็นสิ่งที่ว่าปิดบังว่าบริการจะทำ มันอาจเริ่มมีลักษณะเหมือนการแจกจ่ายสองครั้งในขณะที่งานส่วนใหญ่ยังทำอยู่ในSetStatus()ตัวของมันเอง

เราสามารถตั้งชื่อพารามิเตอร์ 'logger' โดยหวังว่าจะเปิดเผยเจตนาของเรา ดูเหมือนว่าจะอ่อนแอเล็กน้อย

แต่ฉันเลือกที่จะขอIInvoiceLogger(อย่างที่เราทำในตัวอย่างโค้ด) และIInvoiceServiceใช้อินเทอร์เฟซนั้น รหัสลูกค้าสามารถใช้ซิงเกิ้ลนั้นIInvoiceServiceสำหรับInvoiceวิธีการทั้งหมดที่ขอเช่น 'บริการเล็ก ๆ ' โดยเฉพาะอย่างยิ่งในใบแจ้งหนี้ที่แท้จริงในขณะที่ลายเซ็นของวิธีการยังคงทำให้ชัดเจนสิ่งที่พวกเขาขอ

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

public class Invoice
{
    public IEnumerable<CreditNote> GetCreditNotes(ICreditNoteRepository repository)
    { ... }
}

ในความเป็นจริงนี้ให้ทางเลือกในการที่เคยลำบากโหลดขี้เกียจ

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

สำหรับจริงโหลดขี้เกียจคุณสมบัติตามที่ผมทำใช้ในปัจจุบันการฉีดคอนสตรัค แต่ในทางที่ติดตา-ไม่รู้

public class Invoice
{
    // Lazy could use an interface (for contravariance if nothing else), but I digress
    public Lazy<IEnumerable<CreditNote>> CreditNotes { get; }

    // Give me something that will provide my credit notes
    public Invoice(Func<Invoice, IEnumerable<CreditNote>> lazyCreditNotes)
    {
        this.CreditNotes = new Lazy<IEnumerable<CreditNotes>>() => lazyCreditNotes(this));
    }
}

ในมือข้างหนึ่ง, พื้นที่เก็บข้อมูลที่โหลดจากฐานข้อมูลสามารถมีอิสระที่จะเข้าถึงฟังก์ชั่นที่จะโหลดบันทึกเครดิตที่สอดคล้องกันและฟังก์ชั่นที่ฉีดเข้าไปในInvoiceInvoice

ในทางกลับกันโค้ดที่สร้างใหม่ จริงInvoiceจะส่งผ่านฟังก์ชันที่ส่งคืนรายการว่างเท่านั้น:

new Invoice(inv => new List<CreditNote>() as IEnumerable<CreditNote>)

(จารีตประเพณีILazy<out T>สามารถกำจัดพวกเราจากนักแสดงที่น่าเกลียดไปIEnumerableได้ แต่นั่นจะทำให้การอภิปรายซับซ้อนขึ้น)

// Or just an empty IEnumerable
new Invoice(inv => IEnumerable.Empty<CreditNote>())

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


3

สำหรับฉันนี่ดูเหมือนจะเป็นการปฏิบัติที่ดีโดยทั่วไปของ OOD แทนที่จะเป็นเฉพาะ DDD

เหตุผลที่ฉันคิดได้คือ:

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

2

เพียง Vernon Vaughn ให้ทางออก:

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


แต่ไม่ใช่จากเอนทิตี
ช่างตีเหล็ก

จาก Vernon Vaughn IDDD ที่มา: Public class Calendar ขยาย EventSourcedRootEntity {... สาธารณะ CalendarEntry scheduleCalendarEntry (CalendarIdentityService aCalendarIdentityService,
Teimuraz

ตรวจสอบกระดาษของเขา @Teimuraz
Alireza Rahmani Khalili

1

ฉันได้เรียนรู้การเขียนโปรแกรมเชิงวัตถุก่อนที่จะมีเลเยอร์แยกทั้งหมดนี้ปรากฏขึ้นและวัตถุ / คลาสแรกของฉันแมป DID กับฐานข้อมูลโดยตรง

ในที่สุดฉันเพิ่มเลเยอร์กลางเนื่องจากฉันต้องย้ายไปยังเซิร์ฟเวอร์ฐานข้อมูลอื่น ฉันเคยเห็น / ได้ยินเกี่ยวกับสถานการณ์เดียวกันหลายครั้ง

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

ปัจจุบันฉันใช้ 3 เลเยอร์ (GUI, ลอจิก, การเข้าถึงข้อมูล) เหมือนกับที่นักพัฒนาหลายคนทำเพราะเป็นเทคนิคที่ดี

การแยกข้อมูลออกเป็นRepositoryเลเยอร์ (aka Data Accessเลเยอร์) อาจถูกมองว่าเป็นเทคนิคการเขียนโปรแกรมที่ดีไม่ใช่แค่กฎ

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

ข้อความอ้างอิง: อีเลียดไม่ได้ถูกคิดค้นโดยโฮเมอร์ Carmina Burana ไม่ได้คิดค้นคาร์ลออร์ฟทั้งหมดและทั้งสองกรณีบุคคลที่ทำให้คนอื่นทำงานทุกคนได้รับเครดิต ;-)


1
ขอบคุณ แต่ฉันไม่ได้ถามเกี่ยวกับการแยกการเข้าถึงข้อมูลจากตรรกะทางธุรกิจ - นั่นเป็นสิ่งที่ชัดเจนมากที่มีข้อตกลงที่กว้างมาก ฉันถามว่าทำไมในสถาปัตยกรรม DDD เช่น S # arp หน่วยงานไม่ได้รับอนุญาตแม้แต่ 'พูดคุย' กับเลเยอร์การเข้าถึงข้อมูล มันเป็นข้อตกลงที่น่าสนใจที่ฉันไม่สามารถค้นหาการสนทนามากมายเกี่ยวกับ
codeulike

0

สิ่งนี้มาจากหนังสือ Eric Evans Domain Driven Design หรือมาจากที่อื่นหรือไม่

มันเป็นของเก่า หนังสือของ Eric ทำให้มันน่าสนใจขึ้นอีกหน่อย

มีคำอธิบายที่ดีสำหรับเหตุผลอยู่เบื้องหลังที่ไหน?

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

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

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

แก้ไข: เพื่อชี้แจง: ฉันไม่ได้พูดถึงวิธีปฏิบัติแบบ OO แบบคลาสสิกของการแยกการเข้าถึงข้อมูลออกเป็นเลเยอร์แยกต่างหากจากตรรกะทางธุรกิจ - ฉันกำลังพูดถึงการจัดเรียงเฉพาะโดยที่ DDD หน่วยงานไม่ควรพูดคุยกับข้อมูล access layer เลย (เช่นพวกเขาไม่ควรจะเก็บการอ้างอิงไปยังวัตถุ Repository)

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


ขวา. ดังนั้นองค์กรที่ไม่รู้เรื่องนี้จะใช้ตรรกะทางธุรกิจได้อย่างไรหากไม่ได้รับอนุญาตให้พูดคุยกับชั้นความเพียร มันจะทำอย่างไรเมื่อมันต้องการดูค่าในเอนทิตีอื่น ๆ โดยพลการ?
codeulike

หากหน่วยงานของคุณต้องการดูค่าในหน่วยงานอื่นโดยพลการคุณอาจมีปัญหาในการออกแบบ อาจพิจารณาเลิกชั้นเรียนเพื่อให้พวกเขามีความเหนียวแน่นมากขึ้น
cdaq

0

หากต้องการอ้างอิง Carolina Lilientahl "รูปแบบควรป้องกันวงจร" https://www.youtube.com/watch?v=eJjadzMRQAkซึ่งเธออ้างถึงวงจรอ้างอิงระหว่างคลาส ในกรณีของที่เก็บข้อมูลภายในการรวมมีสิ่งล่อใจที่จะสร้างการพึ่งพาแบบวนรอบออกจากการรวมของการนำทางวัตถุเป็นเหตุผลเดียว รูปแบบดังกล่าวข้างต้นโดย prograhammer ที่แนะนำโดย Vernon Vaughn ที่มวลรวมอื่น ๆ มีการอ้างอิงโดยรหัสแทนที่จะเป็นกรณีราก (มีชื่อสำหรับรูปแบบนี้?) แนะนำทางเลือกที่อาจนำไปสู่การแก้ปัญหาอื่น ๆ

ตัวอย่างของการพึ่งพาแบบวนรอบระหว่างคลาส (การรับสารภาพ):

(เวลา 0): สองคลาสตัวอย่างและดีอ้างอิงถึงแต่ละอื่น ๆ (การพึ่งพาแบบวนรอบ) Well อ้างถึง Sample และ Sample อ้างถึง Well จากความสะดวกสบาย (บางครั้งวนลูปตัวอย่างบางครั้งวนลูปทั้งหมดในจาน) ฉันไม่สามารถจินตนาการกรณีที่ตัวอย่างจะไม่อ้างอิงกลับไปที่หลุมที่วางไว้

(เวลา 1): หนึ่งปีต่อมามีการใช้งานกรณีใช้งานจำนวนมาก .... และขณะนี้มีกรณีที่ตัวอย่างไม่ควรอ้างอิงกลับไปที่หลุมที่วางไว้มีแผ่นเพลทชั่วคราวภายในขั้นตอนการทำงาน ที่นี่บ่อน้ำหมายถึงตัวอย่างซึ่งในทางกลับกันหมายถึงบ่อน้ำในอีกจาน ด้วยเหตุนี้บางครั้งพฤติกรรมแปลก ๆ จึงเกิดขึ้นเมื่อมีคนพยายามใช้คุณลักษณะใหม่ ใช้เวลาในการเจาะ

ฉันยังได้รับความช่วยเหลือจากบทความที่กล่าวถึงข้างต้นเกี่ยวกับแง่ลบของการโหลดขี้เกียจ


-1

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


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