Większość osób zaczynając swoją przygodę z Domain-Driven Design jest mocno zafascynowanych encjami. Tak też było w moim wypadku. Prawdę mówiąc, pierwszy system, który wydawało mi się, że buduję w oparciu o zasady DDD nie miał w ogóle rozróżnienia na encje i obiekty wartości. Tych ostatnich po prostu nie było.

Jeśli ktoś zaczął czytać mojego bloga w tym miejscu, krótkie wyjaśnienie. Encja (Entity) to w DDD obiekt posiadający własną tożsamość niezależną od wartości atrybutów. Obiekt wartości (Value Object) to zaś byt tożsamości pozbawiony. Dwa obiekty wartości są uważane za tożsame, jeśli zawierają takie same dane. Dobrą praktyką jest, aby obiekt wartości był niemodyfikowalny (immutable), to znaczy nie możliwa była zmiana wartości jego atrybutów po utworzeniu (jak w klasie String).

Wracając do głównego wątku, dlaczego w ogóle o tym piszę? Czytałem ostatnio sporo postów, głownie dotyczących Entity Framework 4, ale nie tylko, sugerujących, że mapowanie O/RM tabelek w bazie danych na klasy C# (lub jakiegokolwiek innego języka) da nam w efekcie model domeny. Owszem, da — jakiś model. Ale na pewno nie będzie on zgodny z dobrymi praktykami DDD. Brakującym elementem są obiekty wartości.

Dlaczego są one notorycznie pomijane przez wszelkie poradniki dotyczące bibliotek O/RM? Tego nie wiem. Wiem jednak, że obiekty wartości mają kluczowe znaczenie, jeśli chodzi o testowalność modelu domeny. Encje są “grubymi” obiektami, które z definicji akumulują stan. Zmiany stanu encji spowodowane są oczywiście wywoływaniem ich metod. Z drugiej strony efekty wywoływania metod zależą oczywiście od stanu encji w momencie wywołania. Tu pojawiają się schody: aby przetestować obiekt należy go najpierw doprowadzić do odpowiedniego stanu. Jak to zrobić? Są na to dwie możliwości:

  • ustawić wartości pól za pomocą refleksji
  • wywołać odpowiednią kombinację metod

Obie są raczej półśrodkami, niż godnymi polecenia metodami. Wadą pierwszej jest bardzo wysoki stopień powiązania między testami, a implementacją, znacznie ograniczający możliwości refaktoryzacji kodu. Drugi zaś powoduje powstawanie niespecyficznych testów, które chcąc przetestować jedną metodę biznesową, muszą najpierw wywołać inne 4, aby przygotować obiekt. Kiedy błąd wkradnie się do jednej z podstawowych (wczesnych w cyklu życia obiektu) metod, złamany zostanie nie jeden test, ale kilkadziesiąt/kilkaset wykorzystujących tę metodę do przygotowania stanu.

Co mają do tego obiekty wartości? Jak już wspomniałem, powinny one być niemodyfikowalne. Jest to wspaniała cecha, jeśli chodzi o testowalność. Oznacza bowiem, że cały problem z doprowadzeniem obiektu do odpowiedniego stanu magicznie znika! Obiekty wartości otrzymują komplet informacji określających ich stan w konstruktorze. Jeśli chcesz zmodyfikować obiekt wartości — tworzysz po prostu nową instancję.

Brzmi prosto. Cały problem polega na tym, żeby tak budować model domeny, aby logika biznesowa naturalnie trafiała do obiektów wartości, natomiast encje były jedynie odpowiedzialne za koordynowanie odpowiednich wywołań tych obiektów oraz przechowywanie ich instancji jako reprezentacji własnego stanu. Rola encji w modelu powinna się ograniczać do trzech prostych zadań:

  • utrzymywania unikalnej tożsamości
  • utrzymywania relacji z innymi encjami
  • przechowywania stanu w postaci obiektów wartości i (ostatecznie) typów prymitywnych

Wszystko ponadto, a w szczególności skomplikowana logika biznesowa, jest zbędne i może zostać zepchnięte na obiekty wartości. Nawet utrzymywanie relacji z innymi encjami, jeśli wymaga skomplikowanej logiki, może być zrealizowane przez wyspecjalizowany obiekt wartości.

Samo nasuwa się pytanie: dobrze, ale jak? Istnieje wiele sprawdzonych wzorców ułatwiających zadanie wydzielania obiektów wartości ze zbyt “grubych” encji. Przykładem wręcz klasycznym jest wzorzec Quantity/Money Fowlera. Inne obiekty wartości, które ja osobiście stosuję, to m.in. Currency (symbol waluty), Country (symbol kraju), BIC (11-to znakowy kod BIC/SWIFT banku), IBAN (międzynarodowy numer rachunku bankowego) oraz NRB (numer rachunku polskiego).

No dobrze, nawet jeśli się uda zepchnąć całą logikę do obiektów wartości, to i tak encje pozostaną i będą potrzebowały przetestowania. Jak do zrobić? Jestem zdania, że kod pozbawiony logiki warunkowej (a taki powinien być kod encji) wystarczy przetestować za pomocą rozsądnego zestawu testów integracyjnych, ale każdy może mieć swoje ulubione metody. Ważne, żeby testować:-)

VN:F [1.9.13_1145]
Rating: 4.0/5 (1 vote cast)
Testowalność modelu domeny, 4.0 out of 5 based on 1 rating