Value objecty - walidacja

3
Koziołek napisał(a):

PESEL może być poprawny, pomimo że nie spełnia warunków walidacji (taki jego urok). Zwracasz zatem obiekt Pesel, który ma w środku informację o swojej poprawności. Następnie tam, gdzie wymagane jest by pesel był poprawny, odczytujesz tą informację i decydujesz co z nią zrobić.

Dlaczego nie:

  1. Wyjątki – bo niepoprawne dane wejściowe nie są czymś wyjątkowym. Szczególnie w kontekście biznesowym.

To jest błędne podejście. Jeśli mamy konkretny format numeru PESEL który akceptujemy to jak najbardziej przy próbie użycia formatu niepoprawnego mamy sytuację wyjątkową. Value Object jest strażnikiem takich zasad i nie powinien przyjmować niepoprawnych wartości. A już na pewno nie powinien zwracać niczego związanego z walidacją, chyba że jawnie wystawimy metodę w stylu TryParse i zwracającą boolean.

Podam prostszy przykład- jeśli mam VO typu PersonAge zakładając że jest to w kontekście osób już urodzonych, to przekazanie negatywnej wartości jest jak najbardziej wyjątkiem i tymże powinno skutkować.

EDIT: Dopiero zobaczyłem Twój drugi post. Mea culpa.

0

@Aventus: to teraz pytanie podchwytliwe rzucamy wyjątek checked czy unchecked. I drugie trochę mniej podchwytliwe, w którym miejscu go łapiesz?

1

Nie znam na tyle ekosystemu Javy oraz nie pisałem w kontekście konkretnych technologii. Z kolei to gdzie wyjątek jest łapany to już nie zmartwienie VO. O walidacji w VO należy myśleć tak jak o np. parsowaniu wartości. Jeśli spróbujesz sparsować niepoprawny string na int to dostaniesz wyjątek. Inta "nie interesuje" gdzie ten wyjątek jest chwytany, i czy w ogóle.

0
Koziołek napisał(a):

@WyznawcaDDD: ale my mówimy o walidacji, czyli krok przed VO. O tym, jak elegancko powiadomić domenę, że VO nie może być utworzony. Jeżeli później okaże się, że VO zawiera niepoprawne dane, to niech leci nawet System.exit(0).

Rozdział 9, werset 256:
"Grzechem byłoby obarczanie ValueObjectu walidacją lub skażenie jego czystości i nieskazitelności wiedzą o obiekcie służącym do walidacji. Walidacja powinna być odwzorowana w domenie za pomocą specyfikacji."

1

Jeżeli parsuję String na Int i się nie da tego zrobić, to zwracam Either i obsługa tego kontenera gdzieś wyżej. Wyjątki może i ułatwiają pisanie kodu, który je rzuca, ale utrudniają utrzymywanie kodu, który musi sobie z nimi radzić.

@WyznawcaDDD: Wiesz, że Evans pisał swoją książkę w czasach, gdy ludzie odkryli, że Java to COBOL XXI wieku? Mamy tyle pięknych mechanizmów, w różnych językach, które pozwalają na stworzenie VO, będącego czymś innym niż tylko kontenerem na stringi.

0

No ale co jeśli przekazujemy DTOsa który ma jako String taki pesel, w międzyczasie go walidujemy i dopiero jak jest poprawny to próbujemy stworzyć obiekt domenowy? Wtedy to że pesel jest niepoprawny jest sytuacją wyjątkową

0

@scibi92: w takim przypadku nie znamy swojego procesu, bo pomiędzy walidacją, a utworzeniem instancji VO COŚ stało się z danymi.

0

@WyznawcaDDD: Wiesz, że Evans pisał swoją książkę w czasach, gdy ludzie odkryli, że Java to COBOL XXI wieku? Mamy tyle pięknych mechanizmów, w różnych językach, które pozwalają na stworzenie VO, będącego czymś innym niż tylko kontenerem na stringi.

A w którym roku powstała koncepcja SRP, którą tak ochoczo chcesz zbezcześcić?
Eric Evenas pisał o tym że VO jest czymś więcej niż tylko kontenerem i może posiadać rozbudowaną logikę.

Przestań bluźnić!
Dla niewiernych nie ma ratunku.

0

Eric Evenas pisał o tym że VO jest czymś więcej niż tylko kontenerem i może posiadać rozbudowaną logikę.

Jedną z takich logik może być umiejętność odpowiedzenia na pytanie, czy ja mam sens. Najlepiej zrobić to przez typy, ale nie jest to dostępne w każdym języku.

Taki kod, w którym VO nie wie nic o walidacji, a jednocześnie może odpowiedzieć w jakim stanie jest podałem wyżej.

0
Koziołek napisał(a):

Eric Evenas pisał o tym że VO jest czymś więcej niż tylko kontenerem i może posiadać rozbudowaną logikę.

Jedną z takich logik może być umiejętność odpowiedzenia na pytanie, czy ja mam sens. Najlepiej zrobić to przez typy, ale nie jest to dostępne w każdym języku.

Taki kod, w którym VO nie wie nic o walidacji, a jednocześnie może odpowiedzieć w jakim stanie jest podałem wyżej.

Niebieska i Czerwona, księga porusza również implementacje w językach deklaratywnych oraz funkcyjnych. Używanie Optionala jest bez sensu, jeśli nie przeszedłeś walidacji to stop wyświetl komunikat na ekranie "Pole wymagane" (ewentualnie zatrzymaj aplikacje) to nie żaden obiekt opcjonalny. Co do typów to można używać typów jak np. tylko pozytywne liczby itp.

A Either ma ten problem, że nie ma stackTrace i służy do czego innego.

1

A jak ładuję PESELa z raportu z pliku, który czasami jest uwalony? Mam zatrzymywać całą aplikację, bo 0.01% PESELi w raporcie jest uwalonych?

0
Wibowit napisał(a):

A jak ładuję PESELa z raportu z pliku, który czasami jest uwalony? Mam zatrzymywać całą aplikację, bo 0.01% PESELi w raporcie jest uwalonych?

Zrób specyfikacje, która zwraca true,false...?

1
Koziołek napisał(a):

Jeżeli parsuję String na Int i się nie da tego zrobić, to zwracam Either

Zaraz zaraz, mi chodzi o to jak to obsługują istniejące języki jak Java czy C# a nie o to jak sam byś to zrobił. Poza tym jeśli nawet w takim przypadku byś wolał bawić się w swoje Either (które po raz kolejny zaznaczam wymusza więcej babrania się z kodem i sprawdzania wyników) no to faktycznie wyznajemy całkowicie inne filozofie i chyba nie ma sensu dalej ciągnąć dyskusji. Od błędnego parsowania są wyjątki. Od "może" błędnego parsowania- czyli tam gdzie spodziewamy się że coś może nie wyjść- jest coś w stylu TryParse. Jeśli dobrze rozumiem to Either jest czymś jak optional, a nie jest to coś z czego należy korzystać w takim scenariuszu. "Opcjonali" używa się kiedy spodziewamy się że możemy otrzymać coś albo nic. Błędne prasowanie to błędne parsowanie.

@Wibowit

A jak ładuję PESELa z raportu z pliku, który czasami jest uwalony? Mam zatrzymywać całą aplikację, bo 0.01% PESELi w raporcie jest uwalonych

To masz albo całkowicie inne wymagania i błędny pesel w tym kontekście oznacza coś innego (a więc zmieniają się zasady walidacji w samym VO), albo logika biznesowa/domena pozostaje bez zmian i sprawdzasz pesel na poziomie obsługi pliku. Jeśli okazuje się że jest błędny to robisz co trzeba. Przykład trochę bez sensu, nijak ma się do dyskusji. Zakładając że logika biznesowa może pracować tylko z numerami pesel w poprawnym formacie, Twoja obsługa plików w żadnym razie nie powinna wpływać na zasady walidacji w domenie. Jeśli masz błędne numery na etapie migracji danych (z pliku do domeny) to tam je obslugujesz.

0

Czyli walidację PESELa mam mieć gdzieś zupełnie poza klasą reprezentującą PESEL? Po co i dlaczego?

Gdzieś trzeba sprawdzić czy podany String jest prawidłowym PESELem. Gdzie i jak? Załóżmy, że mam 3 źródła PESELi: UI, REST i codzienne raporty w plikach.

0

Po co ci klasa do peselu?

Czyli walidację PESELa mam mieć gdzieś zupełnie poza klasą reprezentującą PESEL? Po co i dlaczego?

Po to, aby walidacja była jawna oraz miała swoją nazwę jak np. "przedawnione faktury", mogła stanowić, część domeny (chociaż nie musi).
W tak banalnym przypadku możesz mieć też fabrykę, która zwróci jakiś result type, jeśli ci tak na tym zależy.

Gdzieś trzeba sprawdzić czy podany String jest prawidłowym PESELem. Gdzie i jak? Załóżmy, że mam 3 źródła PESELi: UI, REST i codzienne raporty w plikach.

To może wróćmy do wersji z optionalem w metodzie fabrykującej w klasie pesel. W jaki sposób przedstawisz w serwisie, który zarządza aplikacją to czy none jest spowodowany walidacją, czy brakiem stringu w bazie danych serwisu restowego? Po co masz za każdym razem sprawdzać tego optionala gdzieś w operacjach domenowych z powodu niepowodzenia walidacji. Czy to ma jakiś sens? Po co tworzyć obiekt z optionalem którego stan none świadczy o tym, że obiekt jest w nieprawidłowym stanie?

2

W tak banalnym przypadku możesz mieć też fabrykę, która zwróci jakiś result type, jeśli ci tak na tym zależy.

Czyli obok klasy Pesel mam mieć PeselFactory do którego przeniosę metodę of? Zaiste potężny zysk.

To może wróćmy do wersji z optionalem w metodzie fabrykującej w klasie pesel. W jaki sposób przedstawisz w serwisie, który zarządza aplikacją to czy none jest spowodowany walidacją, czy brakiem stringu w bazie danych serwisu restowego? Po co masz za każdym razem sprawdzać tego optionala gdzieś w operacjach domenowych z powodu niepowodzenia walidacji. Czy to ma jakiś sens? Po co tworzyć obiekt z optionalem którego stan none świadczy o tym, że obiekt jest w nieprawidłowym stanie?

Optionala czy Eithera mam na etapie ładowania z bazy, raportu, wczytywania z UI bądź RESTa. Gdy dostanę błędny wynik to np pomijam wartość z bazy czy raportu (i loguję ją, by ktoś się tym zajął) albo odrzucam operację na UI czy RESTu. Gdy dostanę Eithera z opisem błędu to mogę zalogować czy podać użytkownikowi konkretny problem. Jeśli nie odrzucę operacji to po wczytaniu danych zostaję z samymi poprawnymi PESELami.

Walidacja PESELa jest raczej jedna i ta sama zawsze, więc po co rozrzucać ją po kilku częściach aplikacji? Nie lepiej trzymać jej jak najbliżej klasy Pesel? W tym przypadku nawet w klasie Pesel bo i tak jest to prosta klasa.

Błąd typu brak kolumny w bazie, pola w JSONie itd to nie są błędy które powinny być obsługiwane w klasie Pesel. Przy takich problemach walidacja w ogóle nie powinna dotrzeć do klasy Pesel, bo przecież nie ma danych do zwalidowania.

0
Wibowit napisał(a):

W tak banalnym przypadku możesz mieć też fabrykę, która zwróci jakiś result type, jeśli ci tak na tym zależy.

Czyli obok klasy Pesel mam mieć PeselFactory do którego przeniosę metodę of? Zaiste potężny zysk.

To może wróćmy do wersji z optionalem w metodzie fabrykującej w klasie pesel. W jaki sposób przedstawisz w serwisie, który zarządza aplikacją to czy none jest spowodowany walidacją, czy brakiem stringu w bazie danych serwisu restowego? Po co masz za każdym razem sprawdzać tego optionala gdzieś w operacjach domenowych z powodu niepowodzenia walidacji. Czy to ma jakiś sens? Po co tworzyć obiekt z optionalem którego stan none świadczy o tym, że obiekt jest w nieprawidłowym stanie?

Optionala czy Eithera mam na etapie ładowania z bazy, raportu, wczytywania z UI bądź RESTa. Gdy dostanę błędny wynik to np pomijam wartość z bazy czy raportu (i loguję ją, by ktoś się tym zajął) albo odrzucam operację na UI czy RESTu. Gdy dostanę Eithera z opisem błędu to mogę zalogować czy podać użytkownikowi konkretny problem. Jeśli nie odrzucę operacji to po wczytaniu danych zostaję z samymi poprawnymi PESELami.

Walidacja PESELa jest raczej jedna i ta sama zawsze, więc po co rozrzucać ją po kilku częściach aplikacji? Nie lepiej trzymać jej jak najbliżej klasy Pesel? W tym przypadku nawet w klasie Pesel bo i tak jest to prosta klasa.

Błąd typu brak kolumny w bazie, pola w JSONie itd to nie są błędy które powinny być obsługiwane w klasie Pesel. Przy takich problemach walidacja w ogóle nie powinna dotrzeć do klasy Pesel, bo przecież nie ma danych do zwalidowania.

W świecie gdzie systemy jedyne co robią, to pobierają pesel i wyświetlają go na ekranie, może miałoby to sens. Pytanie tylko jak rozwiązać problem logiczny związany z optionalem? Chcesz mi powiedzieć, że przejście walidacji jest opcjonalne? Either to też słabo, jeśli miałby zwracać stringa z komunikatem, chyba że będziesz zwracał enuma ze stanem walidacji, ale czy aby na pewno potrzebujemy do tego eithera i po co przekazywać jakikolwiek obiekt dalej skoro nie przeszedł on walidacji? To nagle wszystkie pola i obiekty w domenie trzeba opakować w Eithera? Jak dla mnie to bez sensu.

Nie wiem, czy nadążam pobranie obiektu z REST czy UI == wykonanie walidacji?

0

Właściwie w tej całej debacie do teraz brakuje mi jednego: po co chcesz stworzyć ten obiekt? Bo to też ma znaczenie

0

Pytanie tylko jak rozwiązać problem logiczny związany z optionalem? Chcesz mi powiedzieć, że przejście walidacji jest opcjonalne?

Zwracanie Optionala oznacza iż walidacja jest opcjonalna? Ekhem, nawet nie wiem jak to skomentować.

Either to też słabo, jeśli miałby zwracać stringa z komunikatem, chyba że będziesz zwracał enuma ze stanem walidacji

Na co mi enum? Enum to trochę słabo, bo enum to zestaw stałych. W ogólnym przypadku (nie PESELa) mój błąd może brzmieć za to np: "oczekiwano 5 znaków, a dostałem 3". Jak to zapakować w enuma?

ale czy aby na pewno potrzebujemy do tego eithera i po co przekazywać jakikolwiek obiekt dalej skoro nie przeszedł on walidacji?

Nie przekazujemy dalej. Właśnie o to chodzi. Jak dostanę Lefta to odrzucam żądanie RESTowe, wyświetlam komunikat na UI albo pomijam dane z raportu czy bazy. Jak dostanę Righta to wszystko spoko i go wypakowuję z tego Righa zostając z gołym PESELem w warstwie biznesowej.

To nagle wszystkie pola i obiekty w domenie trzeba opakować w Eithera? Jak dla mnie to bez sensu.
Nie wiem, czy nadążam pobranie obiektu z REST czy UI == wykonanie walidacji?

Either jest podczas ładowania danych z zewnątrz (przy czym baza to też "zewnątrz" jeśli założymy, że ktoś może do niej wepchać uwalonego PESELa, np zapytaniem SQL z palca). Po obsłużeniu błędu zostajesz z gołym VO.

Oczywiście do całego problemu można podejść zupełnie inaczej. Olać walidację w większości miejsc i założyć, że np "slh34t34#$@$dfgsj" jest poprawnym PESELem i orać go w takiej postaci w warstwie biznesowej. Zamiast tego można by dodać metodę isValid do PESELa i korzystać z niej np przy wprowadzaniu danych w UI. W całej reszcie przypadków byłaby nieużywana. Takie podejście jednak ogranicza możliwości klasy Pesel. Z uwalonymi danymi nie jesteśmy w stanie zrobić metody np getBirthDate która gwarantuje, że nie rzuci wyjątku i zawsze zwróci poprawną datę. Gdzieś te błędy muszą lecieć.

0

Zwracanie Optionala oznacza iż walidacja jest opcjonalna? Ekhem, nawet nie wiem jak to skomentować.

Jeśli wynik walidacji zwraca optionala a optional nie jest opcjonalny, to też nie wiem jak to nazwać.

Na co mi enum? Enum to trochę słabo, bo enum to zestaw stałych. W ogólnym przypadku (nie PESELa) mój błąd może brzmieć za to np: "oczekiwano 5 znaków, a dostałem 3". Jak to zapakować w enuma?

Ponieważ komunikat wyświetlany na ekranie a logowany może być inny i zwykle nie walidujemy jednego peselu, tylko całą klasę.

Oczywiście do całego problemu można podejść zupełnie inaczej. Olać walidację w większości miejsc i założyć, że np "slh34t34#$@$dfgsj" jest poprawnym PESELem i orać go w takiej postaci w warstwie biznesowej. Zamiast tego można by dodać metodę isValid do PESELa i korzystać z niej np przy wprowadzaniu danych w UI. W całej reszcie przypadków byłaby nieużywana. Takie podejście jednak ogranicza możliwości klasy Pesel. Z uwalonymi danymi nie jesteśmy w stanie zrobić metody np getBirthDate która gwarantuje, że nie rzuci wyjątku i zawsze zwróci poprawną datę. Gdzieś te błędy muszą lecieć.

Ale ja chcę, aby ona rzuciła wyjątek, ponieważ łatwiej mi się to debuguje, np. kiedy nie przejdzie test, jasno widać na stacktrace co jest wywalone. Daje to elastyczność odnośnie do tego, gdzie jest ta walidacja, ona może być np. tylko w warstwie Webowej, bo łatwiej jest zwalidować cały formularz, którego pesel jest częścią. Jeśli mamy wiele źródeł i walidujemy wyłącznie PESEL to twoja opcja z Either też jest ok.

0

Jeśli wynik walidacji zwraca optionala a optional nie jest opcjonalny, to też nie wiem jak to nazwać.

Stwierdzenie przejście walidacji jest opcjonalne sugeruje, że można walidacji nie odpalić w ogóle, a nie że walidacja opcjonalnie zwróci wartość.

Ponieważ komunikat wyświetlany na ekranie a logowany może być inny i zwykle nie walidujemy jednego peselu, tylko całą klasę.

Może być inny, a może być ten sam. Jeśli walidujemy całą klasę to można np poskładać kilka Eitherów do kupy, opcjonalnie zamieniając je na Validation. Możliwości jest wiele - co kto lubi.

Ale ja chcę, aby ona rzuciła wyjątek, ponieważ łatwiej mi się to debuguje, np. kiedy nie przejdzie test, jasno widać na stacktrace co jest wywalone.

Nie zawsze. Jeśli w message masz "Cannot parse PESEL: too few digits", a potem dostajesz cały stacktrace to co z tym zrobisz? Niewiele. Natomiast jeśli dostaniesz message "Cannot parse PESEL=[23523]: too few digits" to nawet bez stacktrace będziesz miał lepsze info. Either nie wyklucza też exceptionów - możesz zebrać wiadomość błędu z wielu eitherów, a potem rzucić zbiorczego exceptiona.

No i co z tłumaczeniem odpowiedzi na wiele języków?

Wtedy zwracanie sztywnych Stringów staje się problematyczne. Nie zastanawiałem się nad tym, bo nigdy nie miałem potrzeby opisywania błędów w języku innym niż angielski. W takim przypadku (wielojęzycznym) stworzenie osobnego walidatora, który potrafi opisywać błędy w różnych językach na potrzeby np UI byłoby chyba najbardziej sensowne. Gdybym natrafił na taki problem to bym sobie wyrobił opinię.

0

@Wibowit:
A co z bardziej skomplikowanymi klasami jak np. Agregat też byś w nim trzymał walidacje z metodami fabrykującymi Eithera?

0

No niespecjalnie jestem w stanie sobie przypomnieć bym gdzieś robił wysokopoziomową walidację. Zwykle jak sparsuję pojemniki na dane najniższego poziomu (typu wiersz CSVki, pojedynczy formularz, proste obiekty (bez zagnieżdżeń) w JSONie, etc), a potem się okaże że brakuje niektórych (np chcę zrobić Mapę z A do B, mam obiekt typu B, ale brakuje pasującego obiektu typu A) to loguję problem w danym miejscu w logice biznesowej, olewam (pomijam problematyczny obiekt) i jadę dalej. No chyba, że logika wprost polega na sprawdzeniu spójności danych (np po to by wystawić to jako endpoint do sprawdzania stanu zdrowia systemu) - wtedy jednak cała ta logika jest walidacją.

Możesz podać przykład kiedy i jak to robisz (tzn walidujesz agregat)?

0

Nie zawsze. Jeśli w message masz "Cannot parse PESEL: too few digits", a potem dostajesz cały stacktrace to co z tym zrobisz? Niewiele. Natomiast jeśli dostaniesz message "Cannot parse PESEL=[23523]: too few digits" to nawet bez stacktrace będziesz miał lepsze info. Either nie wyklucza też exceptionów - możesz zebrać wiadomość błędu z wielu eitherów, a potem rzucić zbiorczego exceptiona.

A dlaczego nie mogę w message wyjątku mieć np. "The 'Pesel.Number' contains 10 digit/s but should contain 5"?
Jak dla mnie to dużo daje ten stacktrace wiem gdzie był tworzony ten obiekt i jak wyglądał "przepływ w aplikacji".

1

2 strony burzliwej dyskusji jak podejść do walidacji klasy pesel. OOP rulez

1 użytkowników online, w tym zalogowanych: 0, gości: 1