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


10

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

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

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

เนื่องจากความเป็นเอกลักษณ์ของคุณสมบัติที่เป็นของวัตถุคือกฎของโดเมน / ธุรกิจจึงควรจะยาวไปยังโมดูลของโดเมนดังนั้นกฎจึงตรงตามที่ควรจะเป็น

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

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

การออกแบบที่ฉันได้รับการปรับตัวแล้วมีลักษณะเช่นนี้:

class ProductRepository
{
    // throws Repository.RecordNotFoundException
    public Product GetBySKU(string sku);
}

class ProductCrudService
{
    private ProductRepository pr;

    public ProductCrudService(ProductRepository repository)
    {
        pr = repository;
    }

    public void SaveProduct(Domain.Product product)
    {
        try {
            pr.GetBySKU(product.SKU);

            throw Service.ProductWithSKUAlreadyExistsException("msg");
        } catch (Repository.RecordNotFoundException e) {
            // suppress/log exception
        }

        pr.MarkFresh(product);
        pr.ProcessChanges();
    }
}

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

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

วิธีแก้ปัญหาที่ชัดเจนคือการสร้างDomain.ProductCollectionคลาสด้วยAdd(Domain.Product)วิธีการขว้างProductWithSKUAlreadyExistsExceptionแต่มันขาดประสิทธิภาพมากเพราะคุณจะต้องได้รับผลิตภัณฑ์ทั้งหมดจากการจัดเก็บข้อมูลเพื่อหารหัสว่าผลิตภัณฑ์มี SKU เดียวกันหรือไม่ เป็นผลิตภัณฑ์ที่คุณพยายามเพิ่ม

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


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

1
เมื่อใช้คำว่าบริการเราควรพยายามให้มีความชัดเจนมากขึ้นเช่นความแตกต่างระหว่างบริการโดเมนในโดเมนเลเยอร์และแอปพลิเคชันบริการในแอปพลิเคชันเลเยอร์ ดูgorodinski.com/blog/2012/04/14/…
Erik Eidt

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

ข้อความอ้างอิงจากการอ้างอิง "บอกอย่าถาม": "แต่โดยส่วนตัวแล้วฉันไม่ใช้บอกอย่าถาม"
Radarbob

คำตอบ:


7

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

ฉันจะไม่เห็นด้วยกับส่วนนี้ โดยเฉพาะประโยคสุดท้าย

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

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

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


ขอบคุณสำหรับการป้อนข้อมูล ที่เก็บจริง ๆ แล้วสามารถเป็นตัวแทนของDomain.ProductCollectionฉันในใจพิจารณาว่าพวกเขามีความรับผิดชอบในการดึงวัตถุจากชั้นโดเมน?
Andy

@DavidPacker ฉันไม่เข้าใจจริงๆว่าคุณหมายถึงอะไร เพราะไม่จำเป็นต้องเก็บทุกรายการไว้ในหน่วยความจำ การใช้งานเมธอด "DoesNameExist" ควรเป็นแบบสอบถาม SQL (ส่วนใหญ่) ด้านข้อมูลแหล่งข้อมูล
ร่าเริง

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

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

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

4

คุณจะต้องอ่านเกร็กหนุ่มในการตรวจสอบการตั้งค่า

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

ปัญหาของความต้องการ“ เอกลักษณ์” ก็คือบ่อยครั้งที่มีเหตุผลที่ลึกซึ้งกว่าที่ว่าทำไมผู้คนต้องการพวกเขา - Yves Reynhout

คำตอบอีกต่อไป: ฉันเห็นเมนูของความเป็นไปได้ แต่พวกเขาทั้งหมดมีการแลกเปลี่ยน

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

class Product {
    void register(SKU sku, DuplicationService skuLookup) {
        if (skuLookup.isKnownSku(sku) {
            throw ProductWithSKUAlreadyExistsException(...)
        }
        ...
    }
}

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

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

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

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

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

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

ฉันไม่ชอบความต้องการดึง SKU ผลิตภัณฑ์ทั้งหมดออกจากฐานข้อมูลเพื่อดำเนินการในหน่วยความจำ

บางทีคุณไม่จำเป็นต้องทำ หากคุณทดสอบ sku ของคุณด้วยตัวกรอง Bloomคุณสามารถค้นพบ skus ที่เป็นเอกลักษณ์จำนวนมากโดยไม่ต้องโหลดชุดเลย

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

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


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

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