ที่ที่เราควรทำการตรวจสอบความถูกต้องสำหรับโมเดลโดเมน


38

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

public class Order
 {
    private readonly List<OrderLine> _lineItems;

    public virtual Customer Customer { get; private set; }
    public virtual DateTime OrderDate { get; private set; }
    public virtual decimal OrderTotal { get; private set; }

    public Order (Customer customer)
    {
        if (customer == null)
            throw new  ArgumentException("Customer name must be defined");

        Customer = customer;
        OrderDate = DateTime.Now;
        _lineItems = new List<LineItem>();
    }

    public void AddOderLine //....
    public IEnumerable<OrderLine> AddOderLine { get {return _lineItems;} }
}


public class OrderLine
{
    public virtual Order Order { get; set; }
    public virtual Product Product { get; set; }
    public virtual int Quantity { get; set; }
    public virtual decimal UnitPrice { get; set; }

    public OrderLine(Order order, int quantity, Product product)
    {
        if (order == null)
            throw new  ArgumentException("Order name must be defined");
        if (quantity <= 0)
            throw new  ArgumentException("Quantity must be greater than zero");
        if (product == null)
            throw new  ArgumentException("Product name must be defined");

        Order = order;
        Quantity = quantity;
        Product = product;
    }
}

ขอบคุณสำหรับคำแนะนำของคุณ

คำตอบ:


47

มีบทความที่น่าสนใจโดย Martin Fowlerในเรื่องที่เน้นแง่มุมที่คนส่วนใหญ่ (รวมถึงฉัน) มักจะมองข้าม:

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

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

จากนี้ต่อไปนี้ที่ผู้สร้างไม่ควรทำการตรวจสอบยกเว้นบางทีการตรวจสุขภาพจิตขั้นพื้นฐานบางอย่างร่วมกันโดยบริบททั้งหมด

อีกครั้งจากบทความ:

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


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

2
@psr คุณจำเป็นต้องใช้ตรรกะส่วนหลังหรือไม่หากข้อมูลของคุณยังคงอยู่ คุณสามารถปล่อยให้การเปลี่ยนแปลงทั้งหมดอยู่ด้านลูกค้าได้หากข้อมูลของคุณไม่มีความหมายกับโมเดลธุรกิจของคุณ จะเป็นการสิ้นเปลืองทรัพยากรในการส่งข้อความกลับไปกลับมา (client - server) หากข้อมูลไม่มีความหมาย ดังนั้นเราจึงกลับไปสู่อุดมคติของ "ไม่อนุญาตให้วัตถุโดเมนของคุณเข้าสู่สถานะที่ไม่ถูกต้อง!" .
Geo C.

2
ฉันสงสัยว่าทำไมคะแนนโหวตจำนวนมากสำหรับคำตอบที่คลุมเครือ เมื่อใช้ DDD บางครั้งมีกฎบางอย่างที่เพียงตรวจสอบว่าข้อมูลบางส่วนเป็น INT หรืออยู่ในช่วง ตัวอย่างเช่นเมื่อคุณให้ผู้ใช้แอปของคุณเลือกข้อ จำกัด บางอย่างเกี่ยวกับผลิตภัณฑ์ของ บริษัท (มีคนดูตัวอย่างผลิตภัณฑ์ของฉันกี่ครั้งและในช่วงวันใดของเดือน) ข้อ จำกัด ทั้งสองนี้ควรเป็น int และหนึ่งในนั้นควรอยู่ในช่วง 0-31 ดูเหมือนว่าการตรวจสอบความถูกต้องของรูปแบบข้อมูลที่อยู่ในสภาพแวดล้อม DDD ที่ไม่เหมาะสมกับบริการหรือตัวควบคุม แต่ใน DDD ฉันอยู่ด้านข้างของการรักษา validaion ในโดเมน (90% ของมัน)
Geo C.

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

PS ฉันไม่ได้รับอนุญาตผสมกัน (ได้รับอนุญาตให้ทำอะไรบางอย่าง), การรับรองความถูกต้อง (ข้อความมาจากตำแหน่งที่ถูกต้องหรือถูกส่งโดยลูกค้าที่ถูกต้องทั้งสองถูกระบุโดยคีย์ api / โทเค็น / ชื่อผู้ใช้หรืออย่างอื่น) หรือกฎเกณฑ์ทางธุรกิจ เมื่อฉันพูด 90% ฉันหมายถึงกฎเกณฑ์ทางธุรกิจที่ส่วนใหญ่รวมถึงการตรวจสอบความถูกต้องของรูปแบบ การตรวจสอบรูปแบบ Ofcourse สามารถอยู่ในเลเยอร์ด้านบน แต่ส่วนใหญ่จะอยู่ในโดเมน (แม้แต่รูปแบบที่อยู่อีเมลที่จะตรวจสอบความถูกต้องในวัตถุค่า EmailAddress)
Geo C.

5

แม้ว่าคำถามนี้จะค่อนข้างเก่า แต่ฉันต้องการเพิ่มสิ่งที่คุ้มค่า:

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

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

ฉันคิดว่าการใช้การตรวจสอบความถูกต้องตามบริบทและการสร้างวัตถุง่าย ๆ จะทำให้ระบบของคุณใช้งานได้ง่ายขึ้น


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

4

อย่างที่ฉันแน่ใจว่าคุณรู้อยู่แล้ว ...

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

การตรวจสอบความถูกต้องของข้อมูลที่ส่งผ่านเป็นพารามิเตอร์ c'tor ถูกต้องแน่นอนในตัวสร้าง - มิฉะนั้นคุณอาจอนุญาตให้สร้างวัตถุที่ไม่ถูกต้อง

อย่างไรก็ตาม (และนี่เป็นเพียงความคิดเห็นของฉันไม่สามารถหาเอกสารที่ดีได้ในตอนนี้) - หากการตรวจสอบข้อมูลต้องการการดำเนินการที่ซับซ้อน (เช่นการดำเนินการ async - บางทีการตรวจสอบความถูกต้องของเซิร์ฟเวอร์หากพัฒนาแอปเดสก์ท็อป) ใส่ในการเริ่มต้นหรือฟังก์ชั่นการตรวจสอบที่ชัดเจนของการเรียงลำดับบางอย่างและสมาชิกตั้งค่าเริ่มต้น (เช่นnull) ใน c'tor


นอกจากนี้ยังเป็นบันทึกย่อตามที่คุณรวมไว้ในตัวอย่างรหัสของคุณ ...

ถ้าคุณกำลังทำตรวจสอบต่อไป (หรือการทำงานอื่น ๆ ) ในAddOrderLineผมส่วนใหญ่มีแนวโน้มจะเปิดเผยList<LineItem>เป็นทรัพย์สินมากกว่ามีOrderหน้าที่เป็นซุ้ม


ทำไมภาชนะบรรจุถึงเปิดเผย? มันมีความสำคัญอย่างไรกับเลเยอร์ชั้นบนว่าคอนเทนเนอร์คืออะไร? มันสมเหตุสมผลอย่างสมบูรณ์แบบที่จะมีAddLineItemวิธีการ ในความเป็นจริงสำหรับ DDD นี้เป็นที่ต้องการ หากList<LineItem>มีการเปลี่ยนแปลงเป็นวัตถุรวบรวมแบบกำหนดเองคุณสมบัติที่เปิดเผยและทุกอย่างที่ขึ้นอยู่กับList<LineItem>คุณสมบัติอาจมีการเปลี่ยนแปลงข้อผิดพลาดและข้อยกเว้น
IAbstract

4

ควรทำการตรวจสอบความถูกต้องโดยเร็วที่สุด

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

จากคำถามของคุณฉันเดาว่าคำตอบคือการแยกการตรวจสอบออก

  1. การตรวจสอบความถูกต้องของคุณสมบัติตรวจสอบว่าค่าสำหรับคุณสมบัตินั้นถูกต้องเช่นเมื่อช่วงระหว่าง 1-10 ถูก expeced

  2. การตรวจสอบความถูกต้องของวัตถุรับประกันว่าคุณสมบัติทั้งหมดในวัตถุนั้นจะถูกใช้งานร่วมกันได้ เช่น BeginDate อยู่ก่อน EndDate สมมติว่าคุณอ่านค่าจากแหล่งข้อมูลและทั้ง BeginDate และ EndDate จะเริ่มต้นได้ที่ DateTime.Min โดยค่าเริ่มต้น เมื่อตั้งค่า BeginDate จะไม่มีเหตุผลที่จะบังคับใช้กฎ "ต้องเป็นก่อนวันที่สิ้นสุด" เนื่องจากสิ่งนี้ไม่ได้ใช้ YET กฎนี้ควรถูกตรวจสอบหลังจากตั้งค่าคุณสมบัติทั้งหมดแล้ว สิ่งนี้สามารถเรียกได้ที่ระดับรากรวม

  3. การตรวจสอบควรถูก preformed ในเอนทิตีรวม (หรือรากรวม) วัตถุคำสั่งซื้ออาจมีข้อมูลที่ถูกต้องและมันเป็น OrderLines แต่กฎธุรกิจระบุว่าไม่มีคำสั่งซื้ออาจมีมูลค่าเกิน $ 1,000 คุณจะบังคับใช้กฎนี้อย่างไรในบางกรณีการอนุญาตนี้ คุณไม่สามารถเพิ่มคุณสมบัติ "อย่าตรวจสอบจำนวนเงิน" ได้เนื่องจากจะนำไปสู่การใช้งานที่ไม่เหมาะสม (ไม่ช้าก็เร็วคุณอาจจะได้รับ "คำขอที่น่ารังเกียจ" ออกไป)

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

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

เพื่อบอกด้วยคำพูดที่มีชื่อเสียง "มันขึ้นอยู่" แต่อย่างน้อยตอนนี้คุณรู้ว่าทำไมมันขึ้นอยู่

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

ด้วยเหตุนี้ฉันเพียงแค่ตรวจสอบข้อยกเว้นระดับคุณสมบัติ ระดับอื่น ๆ ทั้งหมดที่ฉันใช้ ValidationResult ด้วยวิธี IsValid เพื่อรวบรวม "กฎที่เสียหาย" และส่งต่อให้กับผู้ใช้ใน AggregateException เดียว

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

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

นี่คือเหตุผลที่ฉันยังกล่าวถึงการตรวจสอบการนำเสนอเพื่อลัดวงจรเหล่านี้ให้มากที่สุด

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

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