Udostępnij za pośrednictwem


Walidacje projektu w warstwie modelu domeny

Wskazówka

Ta treść jest fragmentem eBooka "Architektura mikrousług .NET dla konteneryzowanych aplikacji .NET", dostępnego na .NET Docs lub jako bezpłatny plik PDF do pobrania i czytania w trybie offline.

Miniatura okładki eBooka „Architektura mikrousług platformy .NET dla konteneryzowanych aplikacji platformy .NET”.

W DDD reguły walidacji można traktować jako niezmienne. Główna odpowiedzialność agregacji polega na egzekwowaniu niezmienników w trakcie zmian stanu dla wszystkich encji w ramach tej agregacji.

Jednostki domeny powinny zawsze być prawidłowymi jednostkami. Istnieje pewna liczba niezmienników dla obiektu, które zawsze powinny być prawdziwe. Na przykład, obiekt elementu zamówienia musi zawsze zawierać ilość, która jest dodatnią liczbą całkowitą, a także nazwę artykułu i cenę. W związku z tym wymuszanie niezmienności jest obowiązkiem elementów domeny (zwłaszcza korzenia agregatu), a obiekt jednostki nie powinien być w stanie istnieć bez bycia ważnym. Niezmienne reguły są po prostu wyrażane jako kontrakty, a wyjątki lub powiadomienia są zgłaszane, gdy zostaną naruszone.

Przyczyną jest to, że wiele usterek występuje, ponieważ obiekty są w stanie, w których nigdy nie powinny znajdować się.

Proponujemy teraz usługę SendUserCreationEmailService, która przyjmuje obiekt UserProfile... w jaki sposób możemy w tej usłudze uzasadnić, że nazwa nie jest null? Czy sprawdzimy to ponownie? Albo bardziej prawdopodobne... po prostu nie chce ci się sprawdzić i liczysz na najlepsze — liczysz, że ktoś zadbał o jego weryfikację przed wysłaniem do Ciebie. Oczywiście przy użyciu TDD jednym z pierwszych testów powinniśmy napisać jest to, że jeśli wyślem klienta z nazwą null, że powinien zgłosić błąd. Ale kiedy zaczniemy pisać tego rodzaju testy w kółko, zdajemy sobie sprawę... "A co, jeśli nazwa nigdy nie przyjmowała wartości null? To nie musielibyśmy mieć wszystkich tych testów!".

Implementowanie walidacji w warstwie modelu domeny

Walidacje są zwykle implementowane w konstruktorach jednostek domeny lub w metodach, które mogą aktualizować jednostkę. Istnieje wiele sposobów implementowania walidacji, takich jak weryfikowanie danych i zgłaszanie wyjątków w przypadku niepowodzenia walidacji. Istnieją również bardziej zaawansowane wzorce, takie jak używanie wzorca specyfikacji na potrzeby walidacji, oraz wzorzec powiadamiania w celu zwrócenia kolekcji błędów zamiast zwracania wyjątku dla każdej weryfikacji w miarę jego wystąpienia.

Weryfikowanie warunków i zgłaszanie wyjątków

Poniższy przykład kodu przedstawia najprostsze podejście do weryfikacji w jednostce domeny przez zgłoszenie wyjątku. W tabeli odwołań na końcu tej sekcji można wyświetlić linki do bardziej zaawansowanych implementacji na podstawie omówionych wcześniej wzorców.

public void SetAddress(Address address)
{
    _shippingAddress = address?? throw new ArgumentNullException(nameof(address));
}

Lepszym przykładem jest potrzeba zapewnienia, że stan wewnętrzny nie uległ zmianie lub że wystąpiły wszystkie mutacje metody. Na przykład następująca implementacja pozostawi obiekt w nieprawidłowym stanie:

public void SetAddress(string line1, string line2,
    string city, string state, int zip)
{
    _shippingAddress.line1 = line1 ?? throw new ...
    _shippingAddress.line2 = line2;
    _shippingAddress.city = city ?? throw new ...
    _shippingAddress.state = (IsValid(state) ? state : throw new …);
}

Jeśli wartość stanu jest nieprawidłowa, pierwszy wiersz adresu i miasto zostały już zmienione. Może to spowodować, że adres jest nieprawidłowy.

Podobne podejście można użyć w konstruktorze jednostki, podnosząc wyjątek, aby upewnić się, że jednostka jest prawidłowa po jej utworzeniu.

Używanie atrybutów weryfikacji w modelu na podstawie adnotacji danych

Adnotacje danych, takie jak atrybuty Required lub MaxLength, mogą być używane do konfigurowania właściwości pól bazy danych EF Core, jak wyjaśniono szczegółowo w sekcji Mapowanie tabel, ale nie działają już na potrzeby weryfikacji jednostek w EF Core (ani metoda IValidatableObject.Validate), jak to miało miejsce od EF 4.x w .NET Framework.

Adnotacje danych i interfejs IValidatableObject mogą być nadal używane do walidacji modelu podczas wiązania modelu, przed standardowym wywołaniem akcji kontrolera, ale ten model jest przeznaczony do roli ViewModel lub DTO, co jest kwestią związaną z MVC lub API, a nie z modelem domeny.

Po wyjaśnieniu różnicy koncepcyjnej, wciąż można używać adnotacji danych i IValidatableObject w klasie encji do walidacji, jeżeli działania otrzymują parametr obiektu klasy encji, co jednak nie jest zalecane. W takim przypadku walidacja nastąpi po powiązaniu modelu, tuż przed wywołaniem akcji i można sprawdzić właściwość kontrolera ModelState.IsValid, aby sprawdzić wynik. Jednak dzieje się to w kontrolerze, a nie przed utrwaleniem obiektu jednostki w DbContext, tak jak to miało miejsce od EF 4.x.

Nadal można zaimplementować niestandardową walidację w klasie jednostki, korzystając z adnotacji danych oraz metody IValidatableObject.Validate, nadpisując metodę SaveChanges obiektu DbContext.

Przykładowa implementacja weryfikacji IValidatableObject jednostek jest widoczna w tym komentarzu w witrynie GitHub. Ten przykład nie wykonuje weryfikacji opartych na atrybutach, ale powinno być łatwe do zaimplementowania przy użyciu refleksji w tej samej nadpisanej metodzie.

Jednak z punktu widzenia DDD model domeny najlepiej jest utrzymać minimalistyczny, stosując wyjątki w metodach zachowania encji lub przez zaimplementowanie wzorców Specyfikacji i Powiadomień w celu wymuszania reguł walidacji.

Warto używać adnotacji danych w warstwie aplikacji w klasach ViewModel (zamiast jednostek domeny), które będą akceptowały dane wejściowe, aby umożliwić walidację modelu w warstwie interfejsu użytkownika. Nie należy jednak wykonywać tej czynności w przypadku wykluczenia walidacji w modelu domeny.

Zweryfikuj jednostki, implementując wzorzec specyfikacji i wzorzec powiadomień

Na koniec bardziej rozbudowane podejście do implementowania walidacji w modelu domeny polega na zaimplementowaniu wzorca specyfikacji w połączeniu ze wzorcem powiadomień, jak wyjaśniono w niektórych dodatkowych zasobach wymienionych w dalszej części.

Warto wspomnieć, że można również użyć tylko jednego z tych wzorców — na przykład ręcznie weryfikować za pomocą instrukcji sterujących, ale zastosować wzorzec powiadomień, aby ułożyć stos i zwrócić listę błędów walidacji.

Używanie walidacji odroczonej w domenie

Istnieją różne podejścia do obsługi odroczonych weryfikacji w domenie. W swojej książce Implementowanie Domain-Driven Design Vaughn Vernon omawia je w sekcji dotyczącej walidacji.

Weryfikacja dwuetapowa

Rozważ również weryfikację dwuetapową. Użyj walidacji na poziomie pola w obiektach transferu danych (DTO) dla poleceń oraz walidacji na poziomie domeny wewnątrz jednostek. Można to zrobić, zwracając obiekt wynikowy zamiast wyjątków, aby ułatwić radzenie sobie z błędami walidacji.

Używając walidacji pola z adnotacjami danych, na przykład, nie trzeba duplikować definicji walidacji. Jednak wykonanie może być zarówno po stronie serwera, jak i po stronie klienta w przypadku obiektów DTO (na przykład poleceń i modelu ViewModels).

Dodatkowe zasoby