Uczę się DDD i myślę o rzucaniu wyjątków w określonych sytuacjach. Rozumiem, że obiekt nie może wejść w zły stan, więc tutaj wyjątki są w porządku, ale w wielu przykładach wyjątki są zgłaszane na przykład, jeśli próbujemy dodać nowego użytkownika z istniejącą pocztą e-mail w bazie danych.
public function doIt(UserData $userData)
{
$user = $this->userRepository->byEmail($userData->email());
if ($user) {
throw new UserAlreadyExistsException();
}
$this->userRepository->add(
new User(
$userData->email(),
$userData->password()
)
);
}
Jeśli więc użytkownik z tym adresem e-mail istnieje, możemy w usłudze aplikacji wykryć wyjątek, ale nie powinniśmy kontrolować działania aplikacji za pomocą bloku try-catch.
Jak jest na to najlepszy sposób?
Odpowiedzi:
Zacznijmy od krótkiego przeglądu przestrzeni problemowej: jednym z podstawowych zasad DDD jest umieszczenie reguł biznesowych jak najbliżej miejsc, w których należy je egzekwować. Jest to niezwykle ważna koncepcja, ponieważ sprawia, że twój system jest bardziej „spójny”. Przesunięcie reguł „w górę” jest zazwyczaj oznaką modelu anemicznego; gdzie obiekty są tylko workami danych, a reguły są wprowadzane z tymi danymi, które mają być egzekwowane.
Anemiczny model może mieć wiele sensu dla programistów rozpoczynających pracę z DDD. Tworzysz
User
model iEmailMustBeUnqiueRule
dostaje się do niego niezbędne informacje, aby sprawdzić poprawność wiadomości e-mail. Prosty. Elegancki. Problem polega na tym, że ten „sposób myślenia” ma zasadniczo charakter proceduralny. Nie DDD. Ostatecznie dzieje się tak, że masz moduł z dziesiątkamiRules
starannie zapakowanych i zamkniętych, ale są one całkowicie pozbawione kontekstu do tego stopnia, że nie można ich już zmienić, ponieważ nie jest jasne, kiedy / gdzie są egzekwowane. Czy to ma sens? To może być oczywiste, żeEmailMustBeUnqiueRule
zostaną zastosowane na utworzenie grupy roboczejUser
, ale coUserIsInGoodStandingRule
?. Powoli, ale pewnie ziarnistość ekstrakcjiRules
z ich kontekstu pozostawia cię system, który jest trudny do zrozumienia (i dlatego nie można go zmienić). Reguły powinny być enkapsulowane tylko wtedy, gdy faktyczne chrupanie / wykonywanie jest tak szczegółowe, że Twój model zaczyna tracić koncentrację.Teraz do konkretnego pytania: Problem z posiadające
Service
/CommandHandler
rzucićException
to, że logika biznesowa zaczyna przeciekać ( „w górę”) z danej domeny. Dlaczego TwójService
/CommandHandler
musisz znać adres e-mail musi być unikalny? Warstwa usługi aplikacji jest zwykle używana raczej do koordynacji niż do implementacji. Przyczynę tego można zilustrować po prostu, jeśli dodamyChangeEmail
metodę / polecenie do twojego systemu. ZARÓWNO metody / moduły obsługi poleceń będą musiały zawierać unikatowy test. To tutaj deweloper może ulec pokusie „wyodrębnienia” anEmailMustBeUniqueRule
. Jak wyjaśniono powyżej, nie chcemy iść tą drogą.Pewne dodatkowe trudności w zdobywaniu wiedzy mogą prowadzić do odpowiedzi na więcej DDD. Wyjątkowość wiadomości e-mail jest niezmiennikiem, który należy wymusić w całej kolekcji
User
obiektów. Czy w Twojej domenie istnieje koncepcja reprezentująca „kolekcjęUser
obiektów”? Myślę, że prawdopodobnie widzisz, dokąd idę.W tym konkretnym przypadku (i wielu innych, obejmujących egzekwowanie niezmienników w różnych kolekcjach), najlepsze miejsce na wdrożenie tej logiki będzie w twoim
Repository
. Jest to szczególnie wygodne, ponieważRepository
„wiesz” dodatkową infrastrukturę niezbędną do przeprowadzenia tego rodzaju weryfikacji (magazyn danych). W twoim przypadku umieszczę to sprawdzenie wadd
metodzie. To ma sens, prawda? Koncepcyjnie jest to ta metoda, która naprawdę dodajeUser
do twojego systemu. Magazyn danych jest szczegółem implementacji.źródło
Moving rules "upwards" is generally a sign of an anemic model
? W górę oznacza warstwę aplikacji? lub do modelu domeny?Email
model musi stanowić worek danych, który przed wysłaniem jest sprawdzany pod kątem niezmienników. Zobacz: softwareengineering.stackexchange.com/questions/372338/...User
), lub zaakceptowanie faktu, że ten konkretny niezmiennik nie zostanie starannie zamknięty w domenie. Pierwsza z nich reprezentuje rozdzielenie danych i zachowania, co skutkuje paradygmatem proceduralnym. To mniej niż idealne. Walidacja powinna istnieć z danymi, a nie wokół danych. Co więcej, jaką korzyść daje utworzenie usługi domeny, która służy tylko do sprawdzania tej jednej reguły? Pozornie ta usługa ...Repository
,User
i to niezmienne i dlatego nie osiąga purystycznej wizji, do której i tak dążysz . Implementacje repozytoriów są częścią domeny, dlatego można im powierzyć pewne obowiązki.Możesz narzucić weryfikację na każdej warstwie aplikacji. Gdzie narzucić, które reguły zależą od twojej aplikacji.
Na przykład jednostki implementują metody reprezentujące reguły biznesowe, podczas gdy przypadki użycia implementują reguły specyficzne dla aplikacji.
Jeśli budujesz usługę e-mail, taką jak Gmail, można argumentować, że reguła „użytkownicy powinni mieć unikalny adres e-mail” to reguła biznesowa.
Jeśli budujesz proces rejestracji dla nowego systemu CRM, ta reguła prawdopodobnie najlepiej będzie zaimplementowana w przypadku użycia, ponieważ reguła ta prawdopodobnie również będzie częścią przypadku użycia. Ponadto testowanie może być łatwiejsze, ponieważ w testach przypadków użycia można łatwo zlikwidować wywołania repozytorium.
Dla dodatkowego bezpieczeństwa możesz również wymusić tę regułę w swoim repozytorium.
źródło