Nơi chúng ta nên đặt xác nhận cho mô hình miền


38

Tôi vẫn đang tìm cách thực hành tốt nhất để xác nhận mô hình miền. Điều đó có tốt để đặt xác nhận trong hàm tạo của mô hình miền không? ví dụ xác thực mô hình miền của tôi như sau:

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;
    }
}

Cảm ơn tất cả các đề nghị của bạn.

Câu trả lời:


47

Có một bài viết thú vị của Martin Fowler về chủ đề đó làm nổi bật một khía cạnh mà hầu hết mọi người (bao gồm cả tôi) có xu hướng bỏ qua:

Nhưng một điều mà tôi nghĩ rằng liên tục khiến mọi người đi lên là khi họ nghĩ tính hợp lệ của đối tượng theo cách độc lập theo ngữ cảnh như phương thức isValid ngụ ý.

Tôi nghĩ sẽ hữu ích hơn nhiều khi nghĩ về việc xác nhận là một điều gì đó ràng buộc với bối cảnh - thường là một hành động mà bạn muốn làm. Là đơn đặt hàng này có giá trị để được điền, khách hàng này có hợp lệ để đăng ký vào khách sạn. Vì vậy, thay vì có các phương thức như isValid có các phương thức như isValidForCheckIn.

Từ đó, các nhà xây dựng không nên thực hiện xác nhận, ngoại trừ có lẽ một số kiểm tra độ tỉnh táo rất cơ bản được chia sẻ bởi tất cả các bối cảnh.

Một lần nữa từ bài viết:

Trong phần Giới thiệu, Alan Cooper chủ trương rằng chúng ta không nên để ý tưởng về các trạng thái hợp lệ ngăn người dùng nhập (và lưu) thông tin không đầy đủ. Tôi đã được nhắc nhở bởi điều này một vài ngày trước khi đọc một bản thảo của một cuốn sách mà Jimmy Nilsson đang làm việc. Ông đã nêu một nguyên tắc rằng bạn sẽ luôn có thể lưu một đối tượng, ngay cả khi nó có lỗi trong đó. Mặc dù tôi không tin rằng đây là một quy tắc tuyệt đối, tôi nghĩ mọi người có xu hướng ngăn chặn tiết kiệm nhiều hơn họ nghĩ. Suy nghĩ về bối cảnh để xác nhận có thể giúp ngăn chặn điều đó.


Ơn giời có người nói điều này. Các biểu mẫu có 90% dữ liệu nhưng sẽ không lưu bất kỳ thứ gì không công bằng đối với người dùng, những người thường chiếm 10% còn lại chỉ để không mất dữ liệu, vì vậy tất cả các xác thực đã thực hiện là buộc hệ thống mất theo dõi 10% Đã được tạo thành. Các vấn đề tương tự có thể xảy ra ở mặt sau - nhập dữ liệu. Tôi thường thấy tốt hơn là cố gắng làm việc đúng với dữ liệu không hợp lệ hơn là cố gắng ngăn chặn nó xảy ra.
psr

2
@psr Bạn thậm chí có cần logic back-end nếu dữ liệu của bạn không được duy trì? Bạn có thể để tất cả các thao tác ở phía máy khách nếu dữ liệu của bạn không có ý nghĩa gì trong mô hình kinh doanh của bạn. Cũng sẽ lãng phí tài nguyên để gửi tin nhắn qua lại (máy khách - máy chủ) nếu dữ liệu là vô nghĩa. Vì vậy, chúng tôi quay trở lại với ý tưởng "không bao giờ cho phép các đối tượng miền của bạn vào trạng thái không hợp lệ!" .
Geo C.

2
Tôi tự hỏi tại sao rất nhiều phiếu cho một câu trả lời mơ hồ như vậy. Khi sử dụng DDD, đôi khi có một số quy tắc chỉ cần kiểm tra xem một số dữ liệu là INT hoặc nằm trong một phạm vi. Ví dụ: khi bạn cho phép người dùng ứng dụng của bạn chọn một số ràng buộc đối với các sản phẩm của nó (ai đó có thể xem trước sản phẩm của tôi bao nhiêu lần và trong khoảng thời gian ngày nào trong một tháng). Ở đây cả hai ràng buộc phải là int và một trong số chúng phải nằm trong phạm vi 0-31. Điều này có vẻ như xác thực định dạng dữ liệu trong môi trường không DDD sẽ phù hợp với dịch vụ hoặc bộ điều khiển. Nhưng trong DDD tôi đứng về phía giữ validaion trong miền (90% trong số đó).
Geo C.

2
Việc buộc các lớp trên phải biết quá nhiều về miền để giữ nó ở trạng thái hợp lệ có mùi giống như thiết kế xấu. Tên miền phải là tên miền đảm bảo trạng thái của nó là hợp lệ. Di chuyển quá nhiều trên vai của các lớp trên có thể làm cho tên miền của bạn bị thiếu máu và bạn có thể bỏ qua một số hạn chế quan trọng có thể gây tổn hại cho doanh nghiệp của bạn. Những gì tôi nhận ra bây giờ, một sự khái quát hóa phù hợp sẽ là giữ cho xác thực của bạn càng gần với sự kiên trì của bạn càng tốt hoặc càng gần với mã thao tác dữ liệu của bạn (khi nó bị thao túng để đạt đến trạng thái cuối cùng).
Geo C.

PS Tôi không trộn lẫn ủy quyền (được phép làm điều gì đó), xác thực (tin nhắn đến từ đúng vị trí hoặc được gửi bởi đúng khách hàng, cả hai đều được xác định bởi khóa api / mã thông báo / tên người dùng hoặc bất cứ điều gì khác) với xác thực định dạng hoặc quy tắc kinh doanh. Khi tôi nói 90% tôi có nghĩa là những quy tắc kinh doanh mà hầu hết chúng cũng bao gồm xác nhận định dạng. Xác thực định dạng khóa học có thể ở các lớp trên, nhưng hầu hết trong số chúng sẽ ở trong miền (ngay cả định dạng địa chỉ email sẽ được xác thực trong đối tượng giá trị EmailAddress).
Geo C.

5

Mặc dù thực tế câu hỏi này hơi cũ, tôi muốn thêm một cái gì đó đáng giá:

Tôi muốn đồng ý với @MichaelBorgwardt và gia hạn bằng cách đưa ra khả năng kiểm tra. Trong "Làm việc hiệu quả với Bộ luật kế thừa", Michael Feathers nói rất nhiều về những trở ngại trong việc thử nghiệm và một trong những trở ngại đó là các đối tượng "khó xây dựng". Xây dựng một đối tượng không hợp lệ là có thể, và như Fowler gợi ý, kiểm tra tính hợp lệ phụ thuộc vào bối cảnh sẽ có thể xác định các điều kiện đó. Nếu bạn không thể tìm ra cách xây dựng một đối tượng trong khai thác kiểm tra, bạn sẽ gặp khó khăn khi kiểm tra lớp của mình.

Về tính hợp lệ tôi thích nghĩ về các hệ thống kiểm soát. Các hệ thống điều khiển hoạt động bằng cách liên tục phân tích trạng thái của đầu ra và áp dụng hành động khắc phục khi đầu ra lệch khỏi điểm đặt, đây được gọi là điều khiển vòng kín. Điều khiển vòng kín thực chất mong đợi những sai lệch và hành động để sửa chúng và đó là cách thế giới thực hoạt động, đó là lý do tại sao tất cả các hệ thống điều khiển thực thường sử dụng bộ điều khiển vòng kín.

Tôi nghĩ rằng việc sử dụng xác nhận phụ thuộc vào ngữ cảnh và các đối tượng dễ xây dựng sẽ giúp hệ thống của bạn dễ dàng hoạt động hơn.


1
Nhiều khi các đối tượng chỉ xuất hiện khó xây dựng. Ví dụ, trong trường hợp này, bạn có thể bỏ qua hàm tạo công khai bằng cách tạo lớp Wrapper kế thừa từ lớp đang được kiểm tra và cho phép bạn tạo một thể hiện của đối tượng cơ sở ở trạng thái không hợp lệ. Đây là nơi sử dụng các công cụ sửa đổi truy cập chính xác trên các lớp và các nhà xây dựng phát huy tác dụng và thực sự có thể gây bất lợi cho việc kiểm tra nếu sử dụng không đúng cách. Ngoài ra, tránh các lớp và phương thức "niêm phong" trừ khi thích hợp sẽ đi một chặng đường dài để làm cho mã dễ kiểm tra hơn.
P. Roe

4

Như tôi chắc chắn bạn đã biết ...

Trong lập trình hướng đối tượng, một hàm tạo (đôi khi được rút ngắn thành ctor) trong một lớp là một loại chương trình con đặc biệt được gọi khi tạo đối tượng. Nó chuẩn bị đối tượng mới để sử dụng, thường chấp nhận các tham số mà hàm tạo sử dụng để đặt bất kỳ biến thành viên nào được yêu cầu khi đối tượng được tạo lần đầu tiên. Nó được gọi là hàm tạo vì nó xây dựng các giá trị của các thành viên dữ liệu của lớp.

Kiểm tra tính hợp lệ của dữ liệu được truyền dưới dạng tham số c'tor hoàn toàn hợp lệ trong hàm tạo - nếu không bạn có thể cho phép xây dựng một đối tượng không hợp lệ.

Tuy nhiên (và đây chỉ là ý kiến ​​của tôi, không thể tìm thấy bất kỳ tài liệu hay nào về vấn đề này vào thời điểm này) - nếu xác thực dữ liệu yêu cầu các hoạt động phức tạp (như hoạt động không đồng bộ - có lẽ xác thực dựa trên máy chủ nếu phát triển ứng dụng máy tính để bàn), thì tốt hơn đặt một hàm khởi tạo hoặc xác thực rõ ràng của một số loại và các thành viên được đặt thành các giá trị mặc định (chẳng hạn như null) trong c'tor.


Ngoài ra, chỉ là một ghi chú bên cạnh khi bạn đưa nó vào mẫu mã của bạn ...

Trừ khi bạn thực hiện xác nhận thêm (hoặc chức năng khác) AddOrderLine, rất có thể tôi sẽ trưng ra List<LineItem>tài sản đó chứ không phải Orderhoạt động như một mặt tiền .


Tại sao phải phơi container? Điều gì quan trọng đối với các lớp trên những gì container là gì? Nó là hoàn toàn hợp lý để có một AddLineItemphương pháp. Trong thực tế, đối với DDD, điều này được ưa thích. Nếu List<LineItem>được thay đổi thành đối tượng bộ sưu tập tùy chỉnh, thì thuộc tính được hiển thị và mọi thứ phụ thuộc vào thuộc List<LineItem>tính có thể thay đổi, lỗi và ngoại lệ.
Tôi chấp nhận

4

Xác nhận nên được thực hiện càng sớm càng tốt.

Xác thực trong bất kỳ ngữ cảnh nào, mô hình Miền hoặc bất kỳ cách viết phần mềm nào khác, sẽ phục vụ mục đích GÌ bạn muốn xác thực và bạn đang ở cấp độ nào vào lúc này.

Dựa trên câu hỏi của bạn, tôi đoán câu trả lời sẽ là phân chia xác nhận.

  1. Xác thực thuộc tính kiểm tra xem giá trị của thuộc tính đó có đúng không, ví dụ: khi phạm vi từ 1-10 được thực hiện.

  2. Xác thực đối tượng đảm bảo rằng tất cả các thuộc tính trên đối tượng là hợp lệ với nhau. ví dụ: BeginDate trước EndDate. Giả sử bạn đọc một giá trị từ kho lưu trữ dữ liệu và cả BeginDate và EndDate đều được khởi tạo thành DateTime.Min theo mặc định. Khi đặt BeginDate, không có lý do gì để thực thi quy tắc "phải trước EndDate", vì điều này không áp dụng YET. Quy tắc này phải được kiểm tra SAU tất cả các thuộc tính đã được đặt. Điều này có thể được gọi ở cấp gốc tổng hợp

  3. Xác nhận cũng nên được xác định trước trên thực thể tổng hợp (hoặc gốc tổng hợp). Một đối tượng Order có thể chứa dữ liệu hợp lệ và OrderLines cũng vậy. Nhưng sau đó, một quy tắc kinh doanh nói rằng không có đơn hàng nào có thể vượt quá $ 1.000. Làm thế nào bạn sẽ thực thi quy tắc này trong một số trường hợp IS này được cho phép. bạn không thể chỉ thêm một tài sản "không xác thực số tiền" vì điều này sẽ dẫn đến lạm dụng (sớm hay muộn, thậm chí là bạn, chỉ để loại bỏ "yêu cầu khó chịu" này).

  4. tiếp theo là xác nhận ở lớp trình bày. Bạn có thực sự sẽ gửi đối tượng qua mạng, biết rằng nó sẽ thất bại? Hoặc bạn sẽ tha cho người dùng burdon này và thông báo cho anh ta ngay khi anh ta nhập một giá trị không hợp lệ. ví dụ, hầu hết các lần môi trường DEV của bạn sẽ chậm hơn so với sản xuất. Bạn có muốn đợi 30 giây trước khi bạn được thông báo về "bạn đã quên trường này MỘT LẦN trong khi chạy thử KHÁC", đặc biệt là khi có một lỗi sản xuất được khắc phục khi sếp của bạn thở dốc?

  5. Xác nhận ở mức độ bền vững được cho là càng gần với xác thực giá trị tài sản càng tốt. Điều này sẽ giúp ngăn các trường hợp ngoại lệ với việc đọc lỗi "null" hoặc "giá trị không hợp lệ" khi sử dụng trình ánh xạ của bất kỳ loại trình đọc dữ liệu cũ hoặc đơn giản nào. Sử dụng các thủ tục được lưu trữ sẽ giải quyết vấn đề này, nhưng yêu cầu phải viết logic định giá tương tự LẠI và thực hiện nó LẠI. Và các thủ tục được lưu trữ là miền quản trị DB, vì vậy đừng cố gắng thực hiện công việc NGÀI (hoặc tệ hơn là làm phiền anh ta với "việc chọn nitty mà anh ta không được trả tiền".

Vì vậy, để nói với nó với một số từ nổi tiếng "nó phụ thuộc", nhưng ít nhất bây giờ bạn biết TẠI SAO nó phụ thuộc.

Tôi ước tôi có thể đặt tất cả những thứ này ở một nơi duy nhất, nhưng thật không may, điều này không thể được thực hiện. Làm điều này sẽ đặt một sự phụ thuộc vào một "đối tượng của Chúa" chứa TẤT CẢ xác nhận cho TẤT CẢ các lớp. Bạn không muốn đi vào con đường tối đó.

Vì lý do này, tôi chỉ ném ngoại lệ xác nhận một cấp độ tài sản. Tất cả các cấp độ khác tôi sử dụng ValidationResult với phương thức IsValid để thu thập tất cả "quy tắc bị hỏng" và chuyển chúng cho người dùng trong một AggregateException duy nhất.

Khi tuyên truyền ngăn xếp cuộc gọi, sau đó tôi thu thập lại chúng trong AggregateExceptions cho đến khi tôi đến lớp trình bày. Lớp dịch vụ có thể ném ngoại lệ này thẳng đến máy khách trong trường hợp WCF là FaultException.

Điều này cho phép tôi lấy ngoại lệ và tách nó ra để hiển thị các lỗi riêng lẻ ở mỗi điều khiển đầu vào hoặc làm phẳng nó và hiển thị nó trong một danh sách. Sự lựa chọn là của bạn.

đây là lý do tại sao tôi cũng đề cập đến việc xác nhận bản trình bày, để ngắn mạch những điều này càng nhiều càng tốt.

Trong trường hợp bạn đang tự hỏi tại sao tôi cũng có xác nhận ở cấp độ tổng hợp (hoặc cấp độ dịch vụ nếu bạn muốn), thì đó là vì tôi không có quả cầu pha lê cho tôi biết ai sẽ sử dụng dịch vụ của tôi trong tương lai. Bạn sẽ có đủ khó khăn để tìm ra lỗi sai của mình để ngăn người khác mắc lỗi :) bằng cách nhập dữ liệu không hợp lệ. Bạn quản lý ứng dụng A, nhưng ứng dụng B cung cấp một số dữ liệu bằng dịch vụ của bạn. Đoán xem họ hỏi ai đầu tiên khi có lỗi? Quản trị viên của ứng dụng B sẽ vui vẻ thông báo cho người dùng "không có lỗi ở cuối, tôi chỉ cung cấp dữ liệu".

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.