Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

Simon says...

Szymon Pobiega o architekturze i inżynierii oprogramowania
[PL] Mentoring DDD: Poprawność
Jak w większości systemów budowanych w oparciu o DDD, tak i w naszym natrafiliśmy w końcu na problem poprawności obiektów modelu domeny. Na samym początku ograniczyłem się do przekazania programistom informacji, że dobry obiekt modelu powinien być zawsze poprawny.

Pomyślałem, że jest to dobre pierwsze przybliżenie. Szczegółami zajmiemy się później. Szczegóły te miały, według moich przewidywań, dotyczyć walidacji zależnej od kontekstu, która sprawia, że Jeffrey Palermo nie wierzy w zasadę "always valid".

Okazało się jednak, że atak nastąpił z zupełnie innej strony. Problem, który się pojawił dotyczył walidacji unikalności pewnego atrybutu obiektu domeny w obrębie przechowywanego zbioru obiektów. Chodziło konkretnie o login konta użytkownika.

W jaki sposób zaimplementować regułę biznesową: konto użytkownika ma przypisany unikatowy login?

Pierwszym rozwiązaniem, jakie nam się nasunęło na myśl, była Prewencja w warstwie usług.

Usługa "Utwórz konto" po rozpoczęciu transakcji na bazie danych wykorzystuje dedykowaną metodę z Repozytorium Kont, aby sprawdzić, czy istnieje konto o podanym loginie. Jeśli tak, usługa kończy działanie zwracając false. Jeśli konta nie ma, usługa tworzy nowy obiekt Konta i zapisuje go do bazy danych.

Problem z tym podejściem jest jeden: nie działa. Prześledźmy następujący scenariusz:
  1. wątek naszej usługi "Utwórz konto" rozpoczyna transakcję
  2. inny zły wątek rozpoczyna swoją transakcję
  3. nasz wątek sprawdza, czy w bazie są konta o loginie "XXX": nie ma, można kontynuować
  4. zły wątek zapisuje do bazy konto "XXX"
  5. zły wątek zatwierdza swoją transakcję
  6. nasz wątek zapisuje do bazy konto o loginie "XXX" i otrzymuje wyjątek naruszenia constraint-a

Wniosek z tego podejścia: problemu nie da się rozwiązać metodą prewencyjną - musimy liczyć się z wyjątkiem. Ale właściwie to chyba nic strasznego: w końcu sytuacja jest przecież wyjątkowa.

Wydaje nam się, że problem załatwimy przez złapanie wyjątku na poziomie usługi i zwrócenie false do warstwy prezentacji? A co jeśli podjęliśmy wcześniej decyzję o wykorzystaniu zdarzeń domenowych Udiego do komunikacji sytuacji wyjątkwych do GUI? Czy może warstwa usług ma inicjować zdarzenie domenowe? Trochę to nie tak jakbyśmy chcieli:\

A może ktoś ma pomysł, jak regułę unikalności loginu zaimplementować w sposób zgodny z filozofią DDD?

Opublikowane 23 czerwca 2009 20:13 przez simon

Powiadamianie o komentarzach

Jeżeli chciałbyś otrzymywać email gdy ta wypowiedź zostanie zaktualizowana, to zarejestruj się tutaj

Subskrybuj komentarze za pomocą RSS

Komentarze:

# Simon says... : [PL] Mentoring DDD: Poprawność @ 23 czerwca 2009 21:42

Dziękujemy za publikację - Trackback z dotnetomaniak.pl

dotnetomaniak.pl

# re: [PL] Mentoring DDD: Poprawność @ 25 czerwca 2009 00:56

Najczęstszym rozwiązaniem jakie widziałem to zastosowanie IsolationLevel.Serializable. Pytając o istnienie loginu blokujesz dostęp dla innych "złych" wątków. W związku z czym (bazując na Twoim przykładzie) punkt 4 się nie powiedzie.

Wracając do walidacji.

Też mam problem z roztrzygnięciem czy model powinien być zawsze poprawny czy nie. Tu jest dziwna sytuacja, gdyż jeśli model dopuszcza zapisanie w nim takich danych, które mogą być zapisane w bazie bez wyrzucenia wyjątku to w sumie mamy model poprawny.

Owszem w tym samym czasie może nie być poprawny, ale patrząc od strony poprawności biznesowej. Tu walidacja własnie zależy od kontekstu.

Przykładowo mamy obiekt, który ma właściwość Stan i dwa stany: otwarty i zamknięty. W otwartym przykładowa właściwość Rozwiązanie nie jest wymagane. W związku z czym pole w bazie i właściwość musi dopuszczać NULL. To jest poprawne z punktu widzenia biznesowego.

Po zmianie na stan zamknięty pole Rozwiązanie już jest wymagane. Co teraz? Walidacja w tym miejscu zależy od kontekstu (stanu). W którym momencie model nie był/jest poprawny?

dario-g

Co o tym myślisz?

(wymagane) 
wymagane 
(wymagane) 

  
Wprowadź kod: (wymagane)