Walidacja w serwisie czy w repozytorium

Odpowiedz Nowy wątek
2019-02-11 13:23
0

Ostatnio dręczy mnie jedno pytanie: Czy walidacja powinna być wykonywana w serwisie, czy w repozytorium.

Serwis
Większość osób, które o to pytałem, odpowiadała bez wahania: w serwisie, przecież to część logiki biznesowej. Kto to widział, żeby logika biznesowa siedziała w repozytorium!
No ale co na to wszędobylskie constrainty typu UNIQUE czy NOTNULL (masa innych CHECK)? Przecież to walidacja w najczystszej formie i to po stronie repozytorum (a nawet jeszcze głębiej).
Jeśli po stronie serwisu, to wypadałoby wywalić wszystkie constrainty. To natomiast wiąże się z wyjątkowo woooooolną walidacją (z mnóstwem dodatkowych zapytań do bazy danych)

transaction()
    .filter(() -> repository.existsByX(entity.getX)) //X to unique property
    .filter(() -> ...)
    .filter(() -> ...)
    .peek(() -> repository.save(entity));

Repozytorium
Jeśli natomiast walidujemy w repozytorum, jest to umieszczanie tam logiki biznesowej, która czasami może być naprawdę pooookaźna. Podmieniając takie repozytorium na inną implementację trzebaby wiedzieć, że ową walidację trzeba dodatkowo zaimplementować (a to constraintem na bazie, a to jakimiś ifami).

Troche tu, troche tam
Trzecie rozwiązanie: troche tu, troche tam? Dla mnie osobiście brzmi to jak rak, który jest nieutrzymywalny. Jedna odpowiedzialność podzielona nie dość, że na dwa komponenty, to jeszcze takie, które znajdują się w różnych warstwach.

Mimo to zazwyczaj widzę, że większość walidacji jest w serwisie, natomiast część (np. UNIQUE) w repozytorium i tego już serwis nie sprawdza.

No i teraz pytanie: zmieniam repozytorium (bazę danych) na takie, które nie obsługuje constraintów UNIQUE. Muszę dodać walidację albo w serwisie, albo w repozytorium.
Albo zmieniam repozytorium (bazę danych) z takiego, które nie obsługuje UNIQUE do takiej, która obsługuje. Musze wtedy zmienić również serwis, bo przecież trzeba wyrzucić niepotrzebne, wolne sprawdzenie na UNIQUE. Trzeba się mocno pilnować co dany zespół komponentów robi i synchronizować to przy każdej zmianie.
Działając w warstwie repozytorium musze modyfikować warstwę wyżej. Abstrakcja, nie cieknie przez palce. Ona się przelewa!

Co wy na to?

edytowany 1x, ostatnio: Tyvrel, 2019-02-11 14:05

Pozostało 580 znaków

2019-02-11 13:44
0

Wydaje mi się że mylisz walidację po stronie kodu - na przykład Objects.requireNonNull(), czy taką sprawdzającą jakieś uprawnienia danego usera, z taką po stronie db - constrainty.
Poza tym, co konkretnie miałoby zwracać existsByUniqueX? Czy masz na myśli jakis distinct? Nie traktowałbym tego jako walidację, tylko jako dane które chcemy wyciągnąć. Nic nie szkodzi na przeszkodzie, aby nazwać metodę findDistinctByX - czy to jest jakaś walidacja? Idąc dalej, czy ograniczanie warunków w query przez where nadal nazywałbyś walidacją? A wyciąganie większej ilości danych niż potrzebujemy, i filtrowanie ich po backendzie jest złe.

Pozostało 580 znaków

2019-02-11 14:04
0

No to pytanie, gdzie jest ta granica między walidacją do stronie kodu, a walidacją po stronie db. Co walidujemy w której?

PS: UniqueX to property encji, existsByUniqueX zwraca true jeśli istnieje encja w bazie danych o propertisie uniqueX równym parametrowi, false w przeciwnym wypadku.
Używając tego query waliduję warunek UNIQUE w przypadku, gdy nie mam możliwości go ustawienia po stronie db.
Usunę to Unique, bo widze, że jest mylące.

edytowany 2x, ostatnio: Tyvrel, 2019-02-11 14:07

Pozostało 580 znaków

2019-02-11 14:09
2
  1. Integralność danych w bazie to nie jest walidacja.
  2. Co Ty właściwie nazywasz "repozytorium"? Bo z jednej strony to brzmi jakby chodziło Ci o klasę (skoro zestawiasz z serwisem), a z drugiej operujesz pojęciami z baz danych.

"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."

Pozostało 580 znaków

2019-02-11 14:20
0

Jeśli natomiast walidujemy w repozytorum, jest to umieszczanie tam logiki biznesowej, która czasami może być naprawdę pooookaźna

Miejsce walidacji domeny biznesowej jest w domenie. Repozytorium jest warstwa pomiedzy domena a infrastruktura perzystencji, nie powinno tam byc zadnej walidacji (no moze poza podstawowa walidacja typu null check na przekazanym argumencie).

No ale co na to wszędobylskie constrainty typu UNIQUE czy NOTNULL

Takie rzeczy masz w bazie danych. Po co masz sprawdzac takie rzeczy w kodzie? I jak mialbys sprawdzic pole UNIQUE? Ladujac caly zbior danych do pamieci zeby sprawdzic czy inne rekordy nie maja juz takiej wartosci? Domena nie powinna nic wiedziec o perzystencji.


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
Problem chyba polega na tym ze Ty mieszasz idee repozytorium z baza danych. Albo to ja nie ogarniam o co chodzi. Mozesz podac przyklad jak mial by wygladac UNIQUE constraint w repozytorium Twoim zdaniem? Najlepiej przyklad w kodzie (nawet pseudokodzie) w poscie ponizej. - Aventus 2019-02-11 14:54

Pozostało 580 znaków

2019-02-11 14:52
0

@somekind

  1. Co ma wspólnego integralność danych i constraint UNIQUE?

  2. repozytorium nazywam klasę, która się dobiera do bazy danych (SQLem, CBQLem, MongoQLem, jOOQem, czy czymśtam innym) i jako taka wie, jaka jest implementacja bazy danych, więc wie co dana baza danych wspiera. Piszę wszędzie repozytorium, bo piszę o relacji serwis-repozytorium, a serwis nie ma pojęcia o czymś takim jak baza danych. Jeśli coś napisałem dwuznacznie, albo niezrozumiale, to bardzo proszę o wskazanie - ulepszę =)

@Aventus
Czyli według Ciebie Troche tu, troche tam?. UNIQUE, czy NOTNULL po stronie repozytorium (a właściwie głębiej, bo w db), a reszta walidacji w serwisie?

edytowany 4x, ostatnio: Tyvrel, 2019-02-11 14:55

Pozostało 580 znaków

2019-02-11 15:01
0

Bardzo dobre pytanie. Myślę, że niestety - trochę tu, trochę tam, a praktycznie: to co się da (unique, notnull), to w bazie (integralność danych), ale dodatkowo wszystko (!) w warstwie serwisowej, nawet jeśli będzie pewna duplikacja. Dlaczego? Bo to aplikacja decyduje co można a co nie, baza jest szczegółem implementacyjnym. Na przykład pewnego dnia postanawiamy zmienić zródło danych z bazy SQL na dokumentową, albo w ogóle na jakieś zewnętrzne API (wiwat mikroserwisy!). No i mamy problem :)

Drugą sprawą są testy - walidacje w kodzie łatwo się testuje na poziomie unit testów, z bazą już nie tak prosto.

Pozostało 580 znaków

2019-02-11 15:16
eL
0

Ja lubię zasadę Fast Fail więc jeśli coś ma się wysypać to niech się sypie jak najszybciej. Jeśli dostaję jakieś dane na endpoint to już w kontrolerze waliduję czy są poprawne (np. jeśli oczekuje na endpoincie ...users/1 to nie przetwarzam rządania jeśli zamiast 1 - Int - dostałem zły typ). Jeśli sam request jest dobrze zbudowany to wszelkie walidacje robię (i wsumie z tym się też najczęściej spotykam) po stronie serwisów albo ewentualnie warstw zbliżonych do serwisu (jakaś domenowa część systemu). Constrainy w bazie są ewentualnie dodatkowym zabezpieczeniem kiedy ktoś podczas dodawania jakiejś funkcjonalności zapomni zweryfikować jakieś dane (np. unique o którym piszesz).
Walidacja po stronie serwisu jest po prostu łatwiejsza i od razu wiadomo też co się wysypało bo robisz ją przecież warunek po warunku (np. sprawdzasz czy wszystkie pola zostały wypełnione - notnull, czy jest unikatowy np adres - unique itd itd) i możesz od razu zwracać konkretne komunikaty.
Przy okazji nie ma też niepotrzebnych requestów do bazy.

Jeśli miałbym brać surowe dane z endpointa i walić to na bazę bez sprawdzania a w odpowiedzi dostawalbym jakieś wyjątki to bym się chyba pociął jakbym miał to parsować i sprawdzać co poszło nie tak.

Podsumowując, walidacja po stronie kontrolerów i serwisów a constrainy tylko jako dodatek o których można zawsze zapomnieć (jeśli np. tak jak pisze @kelog przeniesiemy bazę np. na mongo).

Dokładnie. Ja się tak ciąłem w jednym projekcie, sporo walidacji typu "wartość musi być podana" było tylko na bazie jako NOTNULL - backend miał cwany exception handler który zamieniał chamsko wszystkie błędy z Hibernate na 400 Bad Request bez żadnej informacji co poszło nie tak, a frontend mógł się tylko domyślać :) - kelog 2019-02-11 15:21

Pozostało 580 znaków

2019-02-11 17:00
1
Tyvrel napisał(a):
  1. repozytorium nazywam klasę, która się dobiera do bazy danych (SQLem, CBQLem, MongoQLem, jOOQem, czy czymśtam innym) i jako taka wie, jaka jest implementacja bazy danych, więc wie co dana baza danych wspiera.

No to to nie jest repozytorium.

UNIQUE, czy NOTNULL po stronie repozytorium (a właściwie głębiej, bo w db), a reszta walidacji w serwisie?

UNIQUE ani NOTNULL nie ma żadnego związku z repozytorium, żadnego związku z aplikacją. To są twory bazodanowe. Czemu je mieszasz?


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."

Pozostało 580 znaków

2019-02-11 22:56
0

Jeśli chodzi o pilnowanie integralności danych w bazie, to ja zawsze zachęcam do stosowania ograniczeń typu CHECK. Czyli czy A jest większe od 0 albo czy kolumny A i C zostały uzupełnione, gdy B > 0. Dane w bazie żyją dłużej niż aplikacja :) A istnienie tego typu walidacji ograniczających radosną twórczość deweloperów znacznie ułatwia późniejszą migrację danych czy podłączenie nowej aplikacji, gdy do poprzedniej... no jakoś nie ma dokumentacji ;)
Tym bardziej jest to zalecane, gdy na danym schemacie hula sobie więcej niż jedna aplikacja, bo i takie sytuacje się zdarzają. Wtedy mamy większą pewność, że dane utworzone przez pierwszą będą spełniać wymagania drugiej aplikacji.
Częste zastosowanie to pilnowanie określonej kombinacji kolumn, gdy są one opcjonalne, ale z pewnych względów aplikacja dopuszcza tylko określone zestawy. Np. należy wypełnić pola A i B albo C i D, ale nigdy A, B i C równocześnie.
Może to się wydawać trywialne, ale gdy tego nie przypilnujemy, a aplikacja też nie przewiduje kreatywnych użytkowników :) to potem kończy się to błędami tego typu:
Kwota = 10 EUR
Kwota w PLN = 0
No co, zero to też liczba :)

Albo:
Kwota = 10 EUR
Kwota w PLN = -40 PLN :D

Pozostało 580 znaków

2019-02-14 12:50
0

Na początku chciałbym podziękować za odpowiedzi i udział w dyskusji =)

@Aventus
@somekind
NOTNULL i UNIQUE używałem jako skrótu myślowego na "walidację, czy dane pole nie jest nullem" oraz "walidację, czy dane pole jest unikalne w danym zbiorze encji".

W mojej małej głowie patrząc z perspektywy serwisu, chcę zwalidować obiekt, który mam. Mogę to zrobić albo sam, albo oddelegować do zadanie.

Nie chce mi się tego sprawdzać samemu, bo np. walidacja "czy dane pole jest unikalne w danym zbiorze encji" jest kosztowna (bo wymaga wykonania dodatkowego zapytania do repozytorium pt.: ej, czy takie coś już istnieje?). Jeśli nie sprawdzam tego sam, to deleguję to zadanie do bazy danych, która wykonuje to za pomocą constraintów.

Ale! Jestem serwisem i mam zależność tylko do repozytorium. Nie widzę, czy za repozytorium jest jakaś baza danych, hash mapa, czy cokolwiek innego. Widzę tylko repozytorium. Więc z mojej perspektywy "czy dane pole jest unikalne w danym zbiorze encji" wykonuje repozytorium. W jaki sposób? A co mnie to obchodzi, jestem serwisem i nie wchodzę w cudzą implementację.

Pod spodem repozytorium wykonuje to zadanie poprzez oddelegowanie tego zadania do bazy danych, która realizuje to poprzez constrainty. Albo moją bazą danych jest HashMapa, więc repozytorium realizuje to zadanie jakimś Javowym kodem.

Liczę że wskażecie mi błąd w moim rozumowaniu, albo wskażecie które warstwy abstrakcji źle interpretuję =(

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