Walidacja w serwisie czy w repozytorium

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?

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.

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.

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

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?

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.

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

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?

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

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ę =(

0

Chyba trochę za bardzo rozdrabniasz tej problem. Twój kod (np. serwis używający obiektu repozytorium) nie powinien wiedzieć jakie constrainty są sprawdzane niżej, to prawda. Tak samo jak repo nie powinno wiedzieć jakie constrainty ma baza danych. Ale nie znaczy to że z tego powodu powinieneś to wszystko sprawdzać w kodzie, ponieważ TY- programista projektujący daną aplikacje WIESZ co, gdzie i jak jest sprawdzane.

0

To, co pisze @Aventus nie jest do końca prawdą. Brzmi to tak jakby mylił constrain z walidacją ale mogę się mylić.

Nie do końca rozumiem o co ci chodzi, pewnie masz jakieś złe założenie odnośnie modelu.

Ale jeśli chodzi ci o walidację poza warstwą aplikacyjną:

To, że spełniasz constrain encji nie musi znaczyć, że cała encja jest prawidłowa dlatego często stosuje się sepecyfikator, który można utożsamiać z "walidacją tą niżej". Poza tym również możesz się spotkać z sytuacją, kiedy trzeba z walidować kompozycje obiektów, a takie rzeczy robi się w serwisie domenowym, fabryce. Jak byś się uparł to możesz to wsadzić do repozytorium, ale to tak jak byś budował piwnicę pod dachem, czyli trochę bez sensu.

2

A to nie jest tak ze są dwie osobne walidacje?
Na poziomie logiki biznesowej walidowane są założenia biznesowe, a na poziomie bazy danych są walidowane jakieś constrainty bazodanowe.

BTW. To co Ty nazywasz repozytorium to bardziej connector/adapter do bazy danych.

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