Nadal szukam sprawdzonych metod sprawdzania poprawności modelu domeny. Czy dobrze jest umieścić walidację w konstruktorze modelu domeny? mój przykład sprawdzania poprawności modelu domeny w następujący sposób:
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;
}
}
Dziękuję za wszystkie sugestie.
źródło
Pomimo tego, że to pytanie jest trochę nieaktualne, chciałbym dodać coś wartościowego:
Chciałbym się zgodzić z @MichaelBorgwardt i rozszerzyć, podnosząc testowalność. W „Skutecznej pracy ze starszym kodem” Michael Feathers dużo mówi o przeszkodach w testowaniu, a jedną z tych przeszkód jest „trudny do zbudowania” obiekt. Konstrukcja niepoprawnego obiektu powinna być możliwa i, jak sugeruje Fowler, kontrole ważności zależne od kontekstu powinny być w stanie zidentyfikować te warunki. Jeśli nie możesz dowiedzieć się, jak skonstruować obiekt w uprzęży testowej, będziesz mieć problemy z testowaniem swojej klasy.
Jeśli chodzi o ważność, lubię myśleć o systemach sterowania. Systemy sterowania działają poprzez ciągłą analizę stanu wyjścia i stosowanie działań naprawczych, gdy wyjście odbiega od wartości zadanej, co nazywa się kontrolą w pętli zamkniętej. Kontrola w zamkniętej pętli z natury oczekuje odchyleń i działa na ich skorygowanie, i tak działa rzeczywisty świat, dlatego wszystkie rzeczywiste systemy sterowania zazwyczaj używają kontrolerów w zamkniętej pętli.
Myślę, że zastosowanie sprawdzania poprawności zależnej od kontekstu i łatwych do zbudowania obiektów sprawi, że Twój system będzie łatwiejszy w pracy na drodze.
źródło
Jestem pewien, że już wiesz ...
Sprawdzanie poprawności danych przekazywanych jako parametry c'tora jest zdecydowanie poprawne w konstruktorze - w przeciwnym razie prawdopodobnie pozwalasz na budowę nieprawidłowego obiektu.
Jednak (i to tylko moja opinia, nie mogę znaleźć na tym etapie żadnych dobrych dokumentów) - jeśli sprawdzanie poprawności danych wymaga złożonych operacji (takich jak operacje asynchroniczne - być może sprawdzanie poprawności na serwerze, jeśli tworzysz aplikację komputerową), to lepiej umieścić jakąś funkcję inicjalizacji lub jawnej walidacji, a członkowie ustawiają wartości domyślne (takie jak
null
) w c'tor.Ponadto, tak jak uwaga dodatkowa, jak to uwzględniono w przykładowym kodzie ...
Jeśli nie dokonasz dalszej weryfikacji (lub innej funkcjonalności)
AddOrderLine
, najprawdopodobniej ujawnię ten obiektList<LineItem>
jako właściwość, niżOrder
działam jako fasada .źródło
AddLineItem
metodę. W rzeczywistości w przypadku DDD jest to preferowane. W przypadkuList<LineItem>
zmiany na niestandardowy obiekt kolekcji właściwość narażona i wszystko, co zależało odList<LineItem>
właściwości, mogą ulec zmianie, błędowi i wyjątkowi.Walidacja powinna zostać przeprowadzona jak najszybciej.
Walidacja w dowolnym kontekście, bez względu na model domeny lub inny sposób pisania oprogramowania, powinna służyć temu, CO chcesz zweryfikować i na jakim poziomie jesteś w tej chwili.
Wydaje mi się, że w oparciu o twoje pytanie podzielimy walidację.
Sprawdzanie poprawności właściwości sprawdza, czy wartość tej właściwości jest poprawna, np. Gdy spodziewany jest zakres od 1 do 10.
Sprawdzanie poprawności obiektu gwarantuje, że wszystkie właściwości obiektu są poprawne w połączeniu ze sobą. np. BeginDate jest przed EndDate. Załóżmy, że odczytujesz wartość ze składnicy danych, a zarówno BeginDate, jak i EndDate są domyślnie inicjowane na DateTime.Min. Podczas ustawiania BeginDate nie ma powodu, aby wymuszać regułę „musi być przed EndDate”, ponieważ nie dotyczy to YET. Tę regułę należy sprawdzić PO ustawieniu wszystkich właściwości. Można to wywołać na poziomie zagregowanego katalogu głównego
Walidacja powinna być również przeprowadzona na jednostce agregującej (lub agregującej root). Obiekt zamówienia może zawierać prawidłowe dane, podobnie jak linie zamówień. Ale wtedy reguła biznesowa mówi, że żadne zamówienie nie może przekroczyć 1000 USD. Jak egzekwowałbyś tę regułę w niektórych przypadkach, jest to dozwolone. nie możesz po prostu dodać właściwości „nie sprawdzaj kwoty”, ponieważ prowadziłoby to do nadużyć (prędzej czy później, być może nawet ciebie, by usunąć tę „nieprzyjemną prośbę”).
następnie następuje walidacja na warstwie prezentacji. Czy naprawdę zamierzasz wysłać obiekt przez sieć, wiedząc, że zawiedzie? A może oszczędzisz użytkownikowi tego burdona i poinformujesz go, gdy tylko wprowadzi nieprawidłową wartość. np. przez większość czasu środowisko DEV będzie wolniejsze niż produkcja. Czy chciałbyś poczekać 30 sekund, zanim zostaniesz poinformowany o „PONOWNIE zapomniałeś tego pola podczas KOLEJNEGO uruchomienia testowego”, zwłaszcza gdy występuje błąd produkcyjny do naprawienia, gdy szef oddycha ci po szyi?
Walidacja na poziomie trwałości powinna być jak najbardziej zbliżona do walidacji wartości właściwości. Pomoże to uniknąć wyjątków związanych z odczytywaniem błędów „null” lub „niepoprawna wartość” przy korzystaniu z jakichkolwiek maperów lub zwykłych starych czytników danych. Korzystanie z procedur przechowywanych rozwiązuje ten problem, ale wymaga napisania tej samej logiki waloryzacji PONOWNIE i wykonania jej PONOWNIE. Procedury składowane są domeną administracyjną DB, więc nie próbuj wykonywać JEGO zadania (lub gorzej: „wybieranie nitty, za które mu nie płaci”).
żeby to powiedzieć znanymi słowami „to zależy”, ale przynajmniej teraz wiesz, DLACZEGO to zależy.
Chciałbym umieścić to wszystko w jednym miejscu, ale niestety nie da się tego zrobić. Spowodowałoby to uzależnienie od „obiektu Boga” zawierającego WSZYSTKIE sprawdzanie poprawności WSZYSTKICH warstw. Nie chcesz iść tą ciemną ścieżką.
Z tego powodu zgłaszam wyjątki sprawdzania poprawności tylko na poziomie właściwości. Wszystkie pozostałe poziomy używam ValidationResult z metodą IsValid, aby zebrać wszystkie „złamane reguły” i przekazać je użytkownikowi w jednym AggregateException.
Podczas propagowania stosu wywołań, zbieram je ponownie w AggregateExceptions, aż dojdę do warstwy prezentacji. Warstwa usługi może zgłosić ten wyjątek bezpośrednio do klienta w przypadku WCF jako wyjątek błędu.
To pozwala mi wziąć wyjątek i podzielić go, aby wyświetlić poszczególne błędy przy każdym elemencie sterującym wejściowym, lub spłaszczyć go i wyświetlić na pojedynczej liście. Wybór nalezy do ciebie.
dlatego wspomniałem również o sprawdzaniu poprawności prezentacji, aby je jak najbardziej zwierać.
Jeśli zastanawiasz się, dlaczego mam również sprawdzanie poprawności na poziomie agregacji (lub na poziomie usługi, jeśli chcesz), to dlatego, że nie mam kryształowej kuli, która mówi mi, kto będzie korzystać z moich usług w przyszłości. Będziesz miał dość problemów ze znalezieniem własnych błędów, aby inni nie popełnili Twoich błędów :) wprowadzając nieprawidłowe dane. Np. Administrujesz aplikacją A, ale aplikacja B podaje pewne dane za pomocą Twojej usługi. Zgadnij, kogo pytają najpierw, kiedy pojawia się błąd? Administrator aplikacji B z przyjemnością poinformuje użytkownika „na moim końcu nie ma błędu, po prostu wprowadzam dane”.
źródło