ใน DDD ที่เก็บควรเปิดเผยเอนทิตีหรือวัตถุโดเมน?


11

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

บางทีรหัสบางอย่างจะอธิบายคำถามของฉันเพิ่มเติม:

เอกลักษณ์

public class Customer
{
  public Guid Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
}

ฉันควรทำอะไรแบบนี้

public Customer GetCustomerByName(string name) { /*some code*/ }

หรืออะไรแบบนี้

public class CustomerDTO
{
  public Guid Id { get; set; }
  public FullName { get; set; }
}

public CustomerDTO GetCustomerByName(string name) { /*some code*/ }

คำถามเพิ่มเติม:

  1. ในที่เก็บฉันควรส่งคืน IQueryable หรือ IEnumerable หรือไม่
  2. ในการให้บริการพื้นที่เก็บข้อมูลหรือฉันควรจะทำสิ่งที่ชอบ .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? หรือเพียงแค่ทำวิธีการที่เป็นGetCustomerBy(Func<string, bool> predicate)อย่างไร

จะเกิดอะไรขึ้นGetCustomerByName('John Smith')ถ้าคุณมี John Smith ยี่สิบคนในฐานข้อมูลของคุณ ดูเหมือนว่าคุณสมมติว่าไม่มีคนสองคนที่มีชื่อเหมือนกัน
bdsl

คำตอบ:


8

ฉันควรส่งคืนข้อมูลเป็นนิติบุคคลหรือวัตถุโดเมน / DTO

นั่นขึ้นอยู่กับการใช้งานของคุณ เหตุผลเดียวที่ฉันสามารถนึกถึงการส่งคืน DTO แทนเอนทิตีแบบเต็มคือถ้าเอนทิตีของคุณมีขนาดใหญ่มากและคุณต้องทำงานกับชุดย่อยของมัน

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

  1. ในที่เก็บฉันควรส่งคืน IQueryable หรือ IEnumerable หรือไม่

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

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

  1. ในบริการหรือที่เก็บฉันควรทำเช่น .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? หรือเพียงแค่สร้างวิธีการที่คล้ายกับ GetCustomerBy (Func predicate)?

ด้วยเหตุผลเดียวกับที่ฉันกล่าวถึงในจุดที่ 1 อย่าใช้GetCustomerBy(Func<string, bool> predicate)แน่นอน มันอาจดูน่าดึงดูดใจในตอนแรก แต่นี่เป็นเหตุผลว่าทำไมผู้คนถึงเรียนรู้ที่จะเกลียดแหล่งเก็บข้อมูลทั่วไป มันรั่ว

สิ่งที่ต้องการGetByPredicate(Func<T, bool> predicate)มีประโยชน์ก็ต่อเมื่อถูกซ่อนอยู่หลังคลาสที่เป็นรูปธรรม ดังนั้นถ้าคุณมีคลาสฐานนามธรรมที่เรียกว่าแบบRepositoryBase<T>สัมผัสprotected T GetByPredicate(Func<T, bool> predicate)ซึ่งถูกใช้โดยที่เก็บข้อมูลคอนกรีตอย่างเดียว (เช่นpublic class CustomerRepository : RepositoryBase<Customer>) ดังนั้นมันก็จะใช้ได้


ถ้าอย่างนั้นคุณก็พูดว่าโอเคที่จะมีคลาส DTO ในเลเยอร์โดเมน?
codefish

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

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

6

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

ตามสิ่งที่ฉันเห็น ...

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

ดังนั้นในบริบทที่คุณจะปรับเปลี่ยนการรวมฉันคาดว่าที่เก็บจะส่งคืนเอนทิตี (หรือที่เรียกว่า root รวม)

2) ตัวจัดการคิวรีไม่ได้แตะมวลรวมเลย แต่จะทำงานร่วมกับการคาดการณ์ของวัตถุรวม - ค่าที่อธิบายสถานะของการรวม / การรวม ณ เวลาใดเวลาหนึ่ง ดังนั้นคิดว่า ProjectionDTO แทนที่จะเป็น AggregateDTO และคุณมีความคิดที่ถูกต้อง

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

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

getCustomersThatSatisfy(Specification spec)

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

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

คำเตือน: จาวาชอบตรวจจับการพิมพ์

interface CustomerRepository {
    interface ConstraintBuilder {
        void setLastName();
        void setFirstName();
    }

    interface ConstraintDescriptor {
        void copyTo(ConstraintBuilder builder);
    }

    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor);
}

SQLBackedCustomerRepository implements CustomerRepository {
    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
        WhereClauseBuilder builder = new WhereClauseBuilder();
        descriptor.copyTo(builder);
        Query q = createQuery(builder.build());
        //...
     }
}

CollectionBackedCustomerRepository implements CustomerRepository {
    List<CustomerProjection> getCustomersThatSatisfy(ConstraintDescriptor descriptor) {
        PredicateBuilder builder = new PredicateBuilder();
        descriptor.copyTo(builder);
        Predicate p = builder.build();
        // ...
}

class MatchLastName implements CustomerRepository.ConstraintDescriptor {
    private final lastName;
    // ...

    void copyTo(CustomerRepository.ConstraintBuilder builder) {
        builder.setLastName(this.lastName);
    }
}

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


ทั้งหมดนี้ดีและดี แต่ผู้ถามไม่ได้พูดถึงการใช้ CQRS คุณพูดถูกแล้วปัญหาของเขาจะไม่มีอยู่จริงถ้าเขาทำ
MetaFight

ฉันไม่มีความรู้เกี่ยวกับ CQRS ดังนั้นฉันจึงได้ยินเรื่องนี้ ตามที่ฉันเข้าใจเมื่อคิดว่าจะส่งคืนอย่างไรหาก AggregateDTO หรือ ProjectionDTO ฉันจะคืน ProjectionDTO จากนั้นgetCustomersThatSatisfy(Specification spec)คือฉันจะแสดงรายการคุณสมบัติที่ฉันต้องการสำหรับตัวเลือกการค้นหา ฉันจะทำให้ถูกต้องหรือไม่
codefish

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

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

1

จากความรู้ของ DDD ของฉันคุณควรมีสิ่งนี้

public CustomerDTO GetCustomerByName(string name) { /*some code*/ }

ในที่เก็บฉันควรส่งคืน IQueryable หรือ IEnumerable หรือไม่

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

ที่เก็บควรส่งคืน IQueryable หรือไม่

ในบริการหรือที่เก็บฉันควรทำเช่น .. GetCustomerByLastName, GetCustomerByFirstName, GetCustomerByEmail? หรือเพียงแค่สร้างวิธีการที่คล้ายกับ GetCustomerBy (Func predicate)?

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

คุณยังสามารถใช้รูปแบบทั่วไปของที่เก็บสำหรับ CRUD ทั่วไปได้หากคุณยังไม่ทราบ

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