Czy DTO mają sens

5
Nofenak napisał(a):

Czy DTO maja sens?

Czy widelce mają sens? Do sałatki, tak. Do zupy, nie.

1

Każdy ma w głowie są dwa wilki. Jeden mówi don’t repeat yourself. Drugi mówi decouple, decouple, decouple.

Wilk od DRY, mówi że DTO nie ma sensu, jeśli ogarniasz cały projekt w twojej głowie. Tzn możesz sobie zrobić skrót myślowy i zacząć od jednej klasy opisującej typ encji, i używać jej we wszystkich warstwach. Taki wewnętrzny ADR:

Jeśli model domenowy się rozejdzie z modelem restowym, to dodam taktyczne mapowanie. Ale na razie będę utrzymywać annotacje JPA i Jacksona na tym samym obiekcie. Heksagon jest w mojej głowie. Będzie śmigać.

Uncle Bob w „Clean Architecture” nawet nazwał ten pattern „Partial Boundaries”.

Wilk od decouple, mówi jednak, że

w praktyce, ta sytuacja występuje bardzo rzadko. To, że ty wiesz, że to jest skrót myślowy nie znaczy, że kolejny junior, Hindus, OE, Genius Tom, Linda będą o tym pamiętali, albo będzie im zależało na dotrzymaniu tego ADR. Zwłaszcza w sytuacjach presji na terminy.

—-

Ostatnio mam taką obserwację, że jak nie wymusisz rozdziału encji od modelu komunikacji, to gwarantujesz sobie problemy w przyszłości. Każdy zespół powtórzy wszystkie antypatterny, które są dobrze znane i opisane w literaturze fachowej.

Więc odpowiadając na pytanie: Tak, DTOsy są potrzebne na każdym miejscu, gdzie twoje dane wychodzą poza proces.

Co jest smutne, bo to też oznacza, że trzeba pisać niesamowite ilości boilerplate, tylko dlatego że typowi programiści to fajtłapy i nieuki.

2

A tak à propos zwracania bezposrednio JPA Entity przez API - to powodzenia ze StackOverflow czy tam OutOfMemoryException, zwłaszcza jak mamy @ ManyToMany 🤡

0
Nofenak napisał(a):

Czy DTO maja sens?

W zastosowaniu do...?

Załóżmy, że mamy kilka DTO - do tworzenia encji, do updatu i do wyświetlania.

Różnią się czymś? Jeśli tak, to odrębne DTO na każdy przypadek ma pewnie sens. Jak się nie różnią - to nie ma. Osobne typy DTO do różnych interakcji z zasadniczo tym samym obiektem / zasobem / tegesem / wichajstrem, jeśli nie ma faktycznej potrzeby by je zróżnicować, to w sumie potencjalne problemy:

  • utrzymujesz więcej kopii tego samego
  • przy rozwijaniu API mogą się faktycznie zacząć rozjeżdżać w niekoniecznie planowany sposób
  • jak ktoś się przyspawa do takiego niechcący rozjechanego API, to wprowadzenie breaking change może oznaczać rok przepychanek politycznych w firmie

Z drugiej strony, jak będziesz trzymać jedno DTO na wszystkie przypadki, a pojawi się potrzeba by jednak się (w przemyślany sposób) rozjechały - to rozjechanie ich na zasadzie w przypadku X pole Y zawsze jest puste jest w sumie jeszcze gorsze, bo wprowadza dodatkowy chaos.

Jeśli chcemy dodać jakieś pole, czy je usunąć czy zmienić to musimy to zrobić zazwyczaj w tych 3 DTO no i jeszcze w tej głównej encji domenowej,

Ale duplikowanie DTO a rozdzielanie DTO, encji domenowej i bazodanowej (ew. DTO po stronie upstreamów) do są dwie różne broszki.

Duplikowanie (lub nie) DTO pod API, które wystawiasz, powinno być w 100% podyktowane potrzebami tego API, i wydzielając sobie DTOsy powinieneś kierować się tym, co jest dobre z perspektywy API, a nie encji domenowych.

W drugą stronę, encje bazodanowe bywają podyktowane tym jakiej bazy używasz, przez co mogą pasować do pozostałych jak pięść do nosa (i często tak jest), w dodatku jak się używa jakichś JPA, Hibernate i innych paści - to są często osmarowane różnymi dziwacznymi adnotacjami, side-effectami z kapelusza robionymi w totalnie nieoczekiwanych (i nieznajdowalnych) okolicznościach, więc generalnie w ciemno bym założył, że chcesz je trzymać w klatce, łańcuchach i zamknięciu.

No a DTO upstreamowych API generalnie mają to do siebie, że nie za bardzo masz wpływ na ich kształt, po prostu bierzesz, co dają.

nie mówiąc, że konstruktory tych wszystkich obiektów mogą być użyte w jeszcze większej ilości miejsc.

Co to ma do rzeczy? Nie wiem w jakim języku piszesz, ale w szanujących się językach (oraz w Javie z Lombokiem) dodanie konstruktora to nie jest wielki wysiłek. Co więcej, konstruktory w liczbie mnogiej sugeruje, że pora pomyśleć o builderach lub ewentualnie fabrykach.

Jakby do tego dodać jeszcze oddzielanie encji biznesnowych od encji JPA to robi się kolejne miejsce, które trzeba zmodyfikować.

IMO jeśli masz dylemat między sklejeniem do kupy DTO API + encja domenowa, lub encja domenowa + encja bazodanowa, to po stokroć wolałbym już sklejać z DTO niż z encją bazodanową. W 90% przypadków na samym początku DTO i model domenowy będą identyczne. Z czasem DTO może zacząć pączkować i np. gdzieniegdzie będziesz potrzebował mocno okrojonego DTO - i wtedy wydzielasz.

Do tego, co jest pod spodem domeny - baz danych, upstream API, jakieś inne farfocle - szkoda się przyspawywać.

Nie wygląda to za ciekawie. DTO są bardzo popularne, ale ostatnio tak nad tym myślałem i wydaje mi się, że w większości przypadków lepiej operować na tym jednym głównym obiekcie.

Ale DTO nie jest nawet od operowania. Nawet w nazwie ma Data Transfer. To jest pojemniczek na dane, w którym wydajesz żądane dane - takie, jak akurat w tym miejscu i czasie uznaliście z klientami API za stosowne przy projektowaniu API. O ile API jest projektowane, a nie tak wyszło 😉

  • Operujesz na obiekcie domenowym.
  • Stosując się do terminologii ports and adapters, porty operują na modelu domenowym, a ewentualne mapowanie na odrębne DTO to szczegół implementacyjny adapterów.
  • Wystawione na zewnątrz API, czy to REST, czy jakiś gRPC, GraphQL, konsumowane eventy itd. to tylko adaptery do tego, co wystawia domena - z modelem domenowym włącznie.
1
Riddle napisał(a):
Nofenak napisał(a):

Czy DTO maja sens?

Czy widelce mają sens? Do sałatki, tak. Do zupy, nie.

screenshot-20241203183251.png
Ale przecież są różne rodzaje widelców, i na przykład większość z tych tutaj nie nadaje się do sałatki pomimo że są widelcami.

Skąd człowiek który ma parę miesięcy doświadczenia ma takie rzeczy wiedzieć? :)

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.