Value objecty - walidacja

Odpowiedz Nowy wątek
2019-08-12 10:50
0

Dzień dobry
Value objecty to świetny sposób na modelowanie domeny, np. klasa Pesel jest o wiele bardziej prezycyjna niż String i łatwiej będzie zrozumiec o co chodzi. Jednak pojawia się pewien "problem".
Jesli mamy taka klase:


public final class Pesel {
 private final String value;

private Pesel(String value) {
  this.value = value;
 }

public static Pesel of(String pesel) {
  return new Pesel(pesel);
 }
}

Czy to nie powinniśmy w takiej metodze wytwórczej sprawdzać czy wartośc jest prawidłowa? Jesli tak, to co zrobić jeśli jest to wartośc nieprawidłowa? Wyrzucac runtimowy expetion?


Nie pomagam przez PM. Pytania zadaje się na forum.
Pokaż pozostałe 16 komentarzy
@danek: no i właśnie to miałem na myśli pisząc o przesłanianiu. - Wibowit 2019-08-12 15:08
Nie używam jak nie muszę, bo mi się nie chcę, ale uważam, że z this jest czytelniej. Narzucanie innym swojej estetyki w tak mało istotnej kwestii jest słabe. Ważne by było spójnie w całym projekcie. I też nie zawsze przeglądam kod w IDE, aż takie to dziwne? - nalik 2019-08-12 15:43
Ależ przeżywacie. Na szczęście z nikim nie musiałem się dogadywać w takiej kwestii, bo nie miałem w projekcie nikogo, kto by nadmiarowe this. wklepywał. W Scali to już absolutnie się takich ludzi nie spodziewam. Nie widziałem ani jednego kawałka Scalowego kodu z nadmiarowym this. - Wibowit 2019-08-12 15:47
W swoich własnych projektach wybieram konwencję z this, w pracowych jak wyjdzie, jest generalnie pół na pół. - somekind 2019-08-12 16:17
Co do review - jak znam taska (bo nad nim na bieżąco współpracowałem albo konsultowałem), to nie muszę kodu uruchamiać. Większość tasków jest jednak na tyle trywialna, że wystarczy rzut oka na GH, żeby stwierdzić, czy jest OK. Poza tym ja zazwyczaj robię PR Hindusom, więc najpierw odrzucam tak długo, aż doprowadzą go do stanu czytelności. Kodu niespełniającego konwencji, norm estetycznych albo zawierającego elementarne błędy (np. brak walidacji wejścia) nie ma sensu pobierać ani uruchamiać. - somekind 2019-08-12 16:17

Pozostało 580 znaków

2019-08-13 08:14
1

1)Jeśli zakładamy że VO tworzymy na podstawie jakiś DTOsów z zewnątrz i one zawierają błędne dane po walidacji to jest to sytuacja wyjątkowa, błąd programisty (zła walidacja DTOsów)
2)Poniekąd racja, ale tutaj po prostu jest to wzglednie łatwe. Jeśli tworzymy pesel to wiadomo jaki String jest niepoprawny ;)
3)Tu się w ogóle nie zgodze. Either ma po prostu left i right i to już pozwala na łatwe sprawdzenie poprawności. Left pozwala na manewrowanie jakością komunikatu i tyle


Nie pomagam przez PM. Pytania zadaje się na forum.
Pokaż pozostałe 10 komentarzy
@danek: Z punktu widzenia tworzącego kod ok, ale jak czytam czyjś kod to zapis pesels.stream().filter(Either::isRight).map(Either::get).forEach(System.out::println)) jakoś nie sugeruje, że wyświetlą się poprawne pesele, ale pesels.stream().filter(Pesel::isValid).map(Pesel::toValid).forEach(System.out::println)) jest bardziej czytelny. - cs 2019-08-13 13:37
niestety trzeba poznać API, żeby dało się od razu zrozumieć (co dość oczywiste przy nowym podejściu) bo żeby uzyskać poprawne starczy zrobić Either.sequence(pesels).get(). Either.sequence() składa liste Eitherów w jeden z listą błednych po lewej, a poprawnych po prawej. Które jest czytelniejsze? Kwestia przyzwyczajenia i znajomości api - danek 2019-08-13 13:59
@cs: Twoje podejście ma cały czas jedną, z mojej perspektywy dużą wadę. Zakładasz, że za każdym razem ktoś najpierw zrobi filter(Pesel::isValid) a dopiero potem .map(Pesel::toValid), a to jest dość odważne stwierdzenie. Twój kod zakłada obostrzenia na poziomie programisty używającego dane API, Either zakłada obostrzenie na poziomie kompilacji - DisQ 2019-08-13 14:11
Either tak samo się zachowa, jeśli zrobię strumień Either<Pesel.Wrong, Pesel.Valid>, bo po obu stronach mamy obiekty tego samego typu bazowego, a w strumieniu są obiekty klasy bazowej. Either nie zabezpiecza przed pomysłem, żeby w obu gałęziach dać klasy pochodne. Każde rozwiązanie coś daje i coś zabiera. - cs 2019-08-13 14:42
możesz też robić List<Object> tylko po co? Nie potrzebujesz, żeby błędna wartość była tego samego typu bo to nic nie daje - danek 2019-08-13 14:43

Pozostało 580 znaków

2019-08-13 09:08
2

Dopiero czytając ten temat dowiedziałem się, że istnieje taka biblioteka jak vavr. Ale po zagłębieniu się w jej dokumentację nasuwa się pytanie: dlaczego nie stosować Validation zamiast Either?

Validation "sumuje" wszystkie błędy, Either wywala się na pierwszym napotkanym błędzie - DisQ 2019-08-13 09:13
@paulonio: można, zależy co chcesz osiągnąć ;) - danek 2019-08-13 09:31
Macie rację. A tak przy okazji dobry artykuł o walidacji za pomocą Either: https://medium.com/@sderosiau[...]robust-java-code-87ccb0be12d7 - paulonio 2019-08-13 09:50

Pozostało 580 znaków

2019-08-14 00:31
2

Either i Optional służy do czego innego niż assercje.
Assercja ValueObjectu to co innego niż Validacja punktu wejścia.
ValueObject powinien zawierć Assercje według Desing By Contract.
Tak mówi pismo święte.

To chyba taki świadek jehowy w kontekście programowania - szydlak 2019-08-14 10:54
Mi się wydaje że to troll po prostu - scibi92 2019-08-14 11:19
Nie od dziś wiadomo, że warunkiem wstępu do funkcji jest czystość argumentów. - vpiotr 2019-08-14 11:51
Niech czysta domena będzie z Tobą. - Charles_Ray 2019-08-14 21:46
I z Tobą bracie @Charles_Ray 'u. - WyznawcaDDD 2019-08-14 22:31

Pozostało 580 znaków

2019-08-14 10:57
0

@scibi92: rzuć okiem na tą książkę: https://www.manning.com/books/functional-programming-in-scala . Nie czytałem, ale na 62 i 213/214 stronie masz przykłady z walidacją za pomocą Either i Validation, może się przyda.

Pozostało 580 znaków

2019-08-14 22:01
1

A może jeszcze inaczej:


public abstract class Pesel{

      private Pesel(String pesel){…}

      private static final class ValidPesel() extends Pesel{…}   
      private static final class InvalidPesel() extends Pesel{…}   

     public abstract boolean isValid();

      public static Pesel of(String pesel){
           PeselValidator.isValid(pesel)? new ValidPesel(pesel): new InvalidPesel(pesel);
      }
}

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.
  2. Optional – bo Empty jest w tym przypadku dwuznaczne. Nie masz tak naprawdę informacji o błędzie, a jedynie słabe założenie w kodzie, że Empty reprezentuje błąd.
  3. Either – to jest kontener, który nie powinien wypływać do biznesu. Jego rolę może przejąć para Valid/InvalidPesel, a jeżeli będą one publiczne i finalne, to można już zrobić coś w rodzaju pattern matchingu przez switch na stringach (brzydko) albo przez wywołania polimorficzne.
Wyjątki – bo niepoprawne dane wejściowe nie są czymś wyjątkowym. Szczególnie w kontekście biznesowym. No a właśnie nie odwrotnie? Ja bym powiedział, że w warstwie domeny niepoprawne dane mogą być czymś wyjątkowym, bo powinien je wyłapać walidator gdzieś bliżej wejścia do aplikacji. - some_ONE 2019-08-14 22:19
@some_ONE: właśnie dyskusja, tak naprawdę, jest o tym co powinien zwrócić walidator. - Koziołek 2019-08-14 22:20

Pozostało 580 znaków

2019-08-14 22:12
0

@Koziołek: tylko to jest trochę specyficzny przypadek (Pesel). Co w sytuacji gdy dla danych danych nie można utworzyć obiektu w ogóle?

2 i 3 pkt: Czym w tym przypadku różni się Optional i Either, że pierwszego można używać, a drugiego nie? Sam kontener nie musi wypływać do biznesu, a jedynie być efektem akcji "na" domenie.


Spring? Ja tam wole mieć kontrole nad kodem ᕙ(ꔢ)ᕗ
Haste - mała biblioteka do testów z czasem.

Pozostało 580 znaków

2019-08-14 22:19
0

@danek: jeżeli nie możesz utworzyć obiektu, to użyj odpowiedniego domenowego odpowiednika Either.

Czym w tym przypadku różni się Optional i Either, że pierwszego można używać, a drugiego nie?

To są dwa różne kontenery. Rozpatrzmy taki przypadek:

Optional<VO> of(String rawData){

    if(rawData == null) return Optional.empty();

    if(validator.validate(rawData))
        return Optional.of(new VO(rawData));
    return Optional.empty();
}

Pytanie, czy jesteś w stanie odpowiedzieć na pytanie, czy Empty oznacza prawidłowy stan – brak wartości, czy też stan błędną wartość?

Either pozwala na rozróżnienie tych dwóch stanów:

Either<VO, Error> of(String rawData){

    if(rawData == null) return Either.left();

    if(validator.validate(rawData))
        return Either.left(new VO(rawData));
    return Either.right(new ValidationError());
}

przy czym w tym przypadku zakładamy, że Either może przyjąć null jako prawidłową wartość.

Pozostało 580 znaków

2019-08-14 22:26
0

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

W kontekście ValueObject niepoprawne dane wejściowe są czymś wyjątkowym, ponieważ nie powinny się tam znaleźć.

Mówi o tym rozdział 9 i 10 niebieskiej księgi według Erica Evansa.

Pozostało 580 znaków

2019-08-14 22:33
0

@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).

Pozostało 580 znaków

2019-08-14 22:33
2
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.


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
edytowany 1x, ostatnio: Aventus, 2019-08-14 22:34

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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