DAO, DTO pattern.

1

Do czego służą dto? Wcześniej myślałem, że dto służą do tego, żeby:

  1. Encje nie miały żadnej logiki, oprócz getterów i setterów, a każdą inną logikę jak (najprostszy przykład który mi przyszedł do głowy) obliczanie pola figury, czy sprawdzanie czy użytkownik jest pełnoletni przenieść do dto. Tzn pobieramy Encję z bazy danych, konwertujemy do dto, wykonujemy operacje, konwertujemy do Encji i zapisujemy znów do bazy danych - jeśli potrzeba.
  2. Przenosić obiekty między warstwami: Załóżmy, że mamy sytuację gdzie w Encji mamy jakąś relację bidirectional, gdybyśmy chcieli takiego Jsona sparsować do Pojo/Entity musielibyśmy oadnotować odpowiednio pola (np @JsonManagedReference, @JsonBackReference)
    Ale na stackoverflow ktoś napisał, że jest to antywzorzec gdyż jest to "krok wstecz" w OO, mowa o punkcie pierwszym - utrudniona jest walidacja i (wg mnie) trochę ciężej jest o rozbudowę kodu ponieważ trzeba "dbać" zarówno jak o Encję tak i o DTO. Krócej mówiąc: punkt pierwszy jest niepoprawny, punkt drugi - może być. W angielskiej wikipedii jest napisane, że DTO różni się od Entity tym, że DTO nie ma żadnej logiki oprócz getterów i setterów, czyli inaczej niż w pkt 1.

Wydaje mi się, że dao służą do tego co repozytoria tak? Z tym, że Repozytoria są "nowocześniejsze" i przyjemniejsze w użyciu (nie trzeba samemu transkacji tworzyć itd).

3

DTO jak sama nazwa wskazuje, służy do transferu danych. W oryginalnym zamyśle chodziło o transfer sieciowy czyli HTTP lub inne oparte o TCP/ IP, UDP, cokolwiek. DTO nie zostały stworzone z myślą o wpakowaniu ich w warstwę bazodanową. Tam siedzi co innego, w zależności od tego jaką abstrakcję sobie wybrałeś na bazę danych i dlatego w tym poście nie będę brał pod uwagę komunikacji z bazką.

Skupiając się na transferze HTTP, DTO używam jako ustandaryzowania sposobu komunikacji RESTowej. DTOsy lądują we współdzielonych między mikroserwisami bibliotekach, w przeciwieństwie do obiektów domenowych, które są prywatne dla każdego mikroserwisu. Obiekty domenowe są zamieniane na DTO przed wysłaniem ich po RESTu i vice versa - obiekty DTO są otrzymywane po zrobieniu żądania RESTowego i są potem zamieniane na obiekt domenowy. Lubię gdy DTOsy są maksymalnie uproszone, czyli nie mają dziedziczenia w żadnej postaci (także implementacji interfejsów) i nie mają problematycznych typów jak LocalDateTime, BigDecimal, etc DTO zamiast nich powinien mieć po prostu Optionale zamiast dziedziczenia czy Stringi zamiast różnorodności typów wartościowych. LocalDateTime, BigDecimale, enumy, hierarchie dziedziczenia, itd mogą siedzieć sobie w klasach domenowych, a w warstwie tłumaczenia pomiędzy klasą domenową, a DTOsem pozbywam się tych abstrakcji lub je odtwarzam (w zależności w którą stronę jest konwersja). Konwersja z DTOsa na obiekt domenowy może się nie powieść i dlatego z niej zwracam Optiona (bo kodzę w Scali, w Javie jest Optional), dzięki czemu kontroler będzie mógł zwrócić odpowiedni błąd HTTP przy obsłudze żądania (np Bad Request). Konwersja z obiektu domenowego (o którego spójność przecież dbamy) na DTOsa powinna powodzić się zawsze i dlatego w niej Optional nie jest potrzebny jako typ zwracany.

Zacytuję jeszcze wiki, bo jest fajny kawałek na https://en.wikipedia.org/wiki/Data_transfer_object :

This pattern is often incorrectly used outside of remote interfaces. This has triggered a response from its author[3] where he reiterates that the whole purpose of DTOs is to shift data in expensive remote calls.

0

Czyli, żebym dobrze to zrozumiał (przykład):
Jeżeli chcielibyśmy przekazać wygenerowane dane (w wzorcu MVC) z modelu do widoku, dobrym nawykiem powinno być przekonwertowanie obiektów domenowych na Dto, tak samo gdyby sytuacja była odwrotna: chcemy Jsona sparsować do Entity to najpierw powinno się do Dto, a dopiero potem do Entity? Albo w sytuacji gdybyśmy chcieli jakieś dane wysłać komuś. Czemu wysyłanie Encji jest złe? Dlatego, że podczas wysyłania mogłaby istnieć możliwość przypadkowych zmian, które mogłyby zostać zapisane/nadpisane w bazie danych?

współdzielonych między mikroserwisami bibliotekach

O np jakich bibliotekach mowisz?
Mówisz, żeby upraszczać maksymalnie Dto (tzn Ty tak robisz) czyli nie ma (w tym przypadku) gotowego Mappera? Co w przypadku gdybyś chciał BigDecimal uprościć? Wszystkie prostsze zmienne tracą (w najlepszym przypadku) na dokładności i mapowanie Entity-Dto-Entity mogłoby do błędów doprowadzić, tak? Czyli w takim przypadku lepiej Stringa użyć?

Jeszcze inne pytanie: Czy Dao dobrze zrozumiałem?

1

Czemu ciągle mieszasz DTO z bazą danych? Jakie encje? DTO ma być do przesyłania danych po sieci. Jak ktoś chce używać DTO do czegoś innego to niech sam sobie dorabia ideologię.

Jeżeli chcielibyśmy przekazać wygenerowane dane (w wzorcu MVC) z modelu do widoku, dobrym nawykiem powinno być przekonwertowanie obiektów domenowych na Dto

Jeśli widok jest generowany po stronie przeglądarki (Angular, React, Vue.js, etc) to po stronie backendu nie ma żadnego widoku. Jest logika biznesowa, warstwy dostępu do danych i końcówki RESTowe. Nie ma widoku - nie ma problemu. Widokiem zajmują się frontendowcy w swoim JSowym kodzie.

chcemy Jsona sparsować do Entity to najpierw powinno się do Dto, a dopiero potem do Entity

Parsowanie JSONowej postaci takiego DTO jakie opisałem (czyli brak dziedziczenia i typów wartościowych typu LocalDateTime, BigDecimal, etc) robi się jedną linijką typu jackson.serialize(), jackson.deserialize() czy jakoś tak.

Albo w sytuacji gdybyśmy chcieli jakieś dane wysłać komuś. Czemu wysyłanie Encji jest złe? Dlatego, że podczas wysyłania mogłaby istnieć możliwość przypadkowych zmian, które mogłyby zostać zapisane/nadpisane w bazie danych?

Gdzie chcesz wysłać i co? Jak rozumiesz wysyłanie? W oryginalnym zamyśle wysyłanie jest po sieci. "Wysyłanie" DTO z klasy do klasy to nie wysyłanie. Mają się zaświecić diody na modemie. Czy mam to jeszcze prościej opisać? Nie ma neta, nie ma przesyłania DTO. Może teraz?

No chyba, że chcesz pominąc krok tłumaczenia obiektu domenowego na DTOsa. Ale od tego nie uciekniesz. Klepanie mapperów jest równoznaczne z klepaniem tłumaczenia do DTOsów, ale jest bardziej pokręcone. Ponadto bardzo ważna sprawa - klasy domenowe powinny być prywatne dla projektu. Wystawiając klasę domenową na zewnątrz utrudniasz jej rozwój, bo musisz synchronizować zmiany w niej z innymi systemami (tzn włączając w to frontend po stronie przeglądarki). Klasy domenowe nie powinny wychodzić na zewnątrz.

Mówisz, żeby upraszczać maksymalnie Dto (tzn Ty tak robisz) czyli nie ma (w tym przypadku) gotowego Mappera? Co w przypadku gdybyś chciał BigDecimal uprościć?

Ja tak robię po to by nie bawić się w żadne niestandardowe mappery tylko zrobić normalne metodki do konwersji między DTOsami a domeną biznesową. Jeśli uproszczę DTOsa to mogę w ogóle zapomnieć jakiego używam serializera do JSONa, mogę mieć ich 5 i żonglować nimi w zależności od humoru. Ponadto wiele deserializerów ma brzydką konwencję rzucania wyjątków podczas niemożliwości konwersji DTOsa na obiekt domenowy. https://en.wikipedia.org/wiki/Coding_by_exception niespecjalnie mi się podoba

2

@Aisekai pierwszy problem jest taki, że operujecie zupełnie odmiennymi pojęciami. Ty piszesz o Encjach myśląc o klasach Entity w sensie jakiegoś JPA/Hibernate, które de facto zwykle są... zwykłymi DTO! Wibowit pisze o Encjach w sensie Klas Domenowych. Jedno z drugim nie ma nic wspólnego.

Klasy @Entity służą, jak nie trudno zauważyć, do wymiany informacji z zewnętrznym systemem, jakim jest baza danych, stąd też są DTO. To są wszystko po prostu struktury danych służące do tego żeby wciągnąć jakieś dane z zewnatrz, albo wypchnąć na zewnątrz. Nic więcej.

0

DTO pochodzi z czasów EJB, dawno temu encje EJB były serializowalne, tak żeby przesyłać to po sieci pomiędzy JVM, a to po to, że w teorii mogłeś mieć aplikację webową na jednym serwerze, która korzysta z logiki na innym serwerze ("zdeployowane" EJB). Specyfikacji JEE nakładała szereg wymagań na to jak EJB powinno wyglądać (tj. oprócz danych, jakie metody implementować). Takie obiekty były "ciężkie", wymyślono więc lżejsze obiekty "DTO", które miały przenosić dane (i tylko dane) po sieci, między komponentami.

Wzorce typowo (GOF) dzielą się na: Kreacyjne, strukturalne, behawioralne. Jak spojrzysz do tego katalogu, to nie zobaczysz tam DTO. Dla odmiany, jeśli spojrzysz na http://www.corej2eepatterns.com/ to tam DTO jest, ale sklasyfikowany w zupełnie innej taksonomii (warstwa biznesowa, prezentacji, integracyjna) i jest na styku warstwy biznesowej i integracyjnej (przesyłanie danych).

Mówiąc o jakimś wzorcu trzeba mieć na uwadze: KONTEKST i PROBLEM, który wzorzec rozwiązuje. Dla DTO kontekstem jest J2EE i EJB, zaś PROBLEMEM jest nadmierny transfer danych (tę część szczegółowo wyjaśnił @Wibowit) . Jeśli ktoś używa DTO w innym kontekście, to powinien to zaznaczyć i wskazać jaki problem rozwiązuje. Jeśli tego nie zrobi, to zakłada, że rozmówca wie o czym jest rozmowa (a rozmówca może mieć na myśli coś innego ;-) i nieporozumienie gotowe).

0

Jeszcze gdzieś można się natknąć na Value Object - Point, LocalDate.
Chyba Fowler trochę zmienił jego definicję ostatnio, ale mogę się mylić (jest bardziej pod FP).

0

Value Object przydaje się w klasach domenowych. Zamiast String accountName można zrobić AccountName accountName (tutaj to akurat widać pewną nadmiarowość, ale z varem już jej nie będzie; poza tym lepiej mieć trochę nadmiarowości niż błędy) i mieć silne typowanie zamiast "stringly typed programming".

Klaski które wrzucam do bazki nie nazywam DTOsami. Rządzą się zresztą innymi prawami. Używam Slicka (slick.lightbend.com), więc klasy lecące do bazy są odwzorowaniem 1:1 wierszy tabel w bazie. Są wprost IDki, tabele łącznikowe zamiast kolekcji itd Jest do tego warstwa tłumaczenia między klasami domenowymi (zawierającymi kolekcje) i klasami bazodanowymi (gdzie kolekcje są reprezentowane przed IDki w tabelach łącznikowych bądź nie - zależnie od typu relacji).

O ile w DTOsie chcę zamieniać każdy typ wartościowy na Stringi (oprócz liczb o typach podstawowych, których oczywiście nie warto w ten sposób zamieniać) o tyle w obiekcie bazodanowym chcę mieć sql.Timestamp, BigDecimala itd po to, by mieć odpowiadające im typy w bazie danych i móc na nich robić zapytania typu "kolumna_z_liczbą > 5.34 && kolumna_z_liczbą < 34.3". Obsługa kolekcji też jest inna. W DTOsie zostawiam kolekcje tak jak są, bo JSON sobie z nimi bez problemu radzi. Bazce natomiast trzeba przerobić kolekcje na IDki z lub bez tabel łącznikowych.

W Hibernate i innych magicznych ORMach na pewno się to będzie różnić, ale z Hibernatem mam nikłe doświadczenie. Nie pracowałem nigdy w projekcie, który sensownie wykorzystywał Hibernate'a i już chyba tego nie zrobię (bo po co?). Slick umożliwia w razie potrzeby operowanie czystym SQLem do insertów, selectów, updateów, etc nie tracąc w całości typowania danych wyciąganych z bazki. Hibernate/ JPA/ etc to trochę utrudnia wprowadzając swoją abstrakcję na SQLa (jakś tam JPQL czy cóś). Musimy orientować się i sprawdzać jak ten JPQL tłumaczy się na SQLa co niepotrzebnie zwiększa czas na tworzenie zapytania. Ręczne klepanie SQLa robi się w trudnych przypadkach, gdy chcemy zrobić skomplikowane, ale szybkie zapytanie i dostrajamy to zapytanie pod konkretną bazkę. Niespecjalnie jest sens wprowadzać kolejną abstrakcję w postaci JPQL w takim przypadku. Dla Javy trochę podobną rzeczą co Slick jest https://www.jooq.org/ (nie jest aż tak sprytny jak Slick, ale jest znacznie lepszy niż klejenie stringów wprost, nawet PreparedStatementów).

0

No własnie Encji JPA bym nie nazywał do końca DTOsami ze wględu na magię JPA typu dirty checking, dziedziczenie klas itd.

0
Wibowit napisał(a):

DTO ma być do przesyłania danych po sieci. Jak ktoś chce używać DTO do czegoś innego to niech sam sobie dorabia ideologię.

Chyba zapomniałeś o DTO służących do komunikacji pomiędzy stroną internetową a biznesem, tak jak w Spring MVC. Dostaję dane z formularza na www i chcę je zapisać do bazy danych. Muszę mieć DTO, choć nie ma tu przesyłania danych po sieci. Wszystko już jest u mnie, na serwerze Javy. Chodzi o izolację danych, zabezpieczenie przed przeciekaniam. W obie strony.

Ogólnie rzecz biorąc pojęcie DTO jest dość szerokie. Zawsze, gdy znajdzie się jakiś powód, by obiektu nie przekazywać w całości - trzeba stworzyć DTO. Rodzaj kopii danych, najczęściej wybiórczy. I może to być nawet w ramach tego samego systemu, pomiędzy np. dwoma częściami jednej aplikacji. Zobaczę jeszcze na wiki, czy nie zaprzeczam definicji DTO... :) Zaprzeczam, ale do wiki ktoś sobie dorzucił jedno zdanie, niby autorstwa Fowlera, ale wyrwane z kontekstu i zinterpretowane. Zresztą jest to zdanie cytowane przez Wibowita. A Martin Fowler też podaje przykłady DTO lokalnych, w artykule LocalDTO.

No ale to nic nie zmienia, czy może być lokalne DTO czy nie. Chyba nie ma się o co spierać. Nawet jak ktoś stworzy sobie lokalny obiekt do przenoszenia danych między modułami, to nazwie je DTO. Trudno odmówić mu do tego prawa :) Wtedy nie będzie się przejmował serializacją do stringów. Rzeczywiście, trochę inny charakter takiego DTO lokalnego.

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