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.

0

Tak, Shalom miał rację. Mówiąc o Encjach miałem na myśli klasy oadnotowane @Entity, więc przepraszam za nieporozumienia.
Teraz żebym się upewnił, bo dotąd nie znałem pojęcia klasa domenowa i tylko słyszałem o klasie biznesowej, że istnieje, więc poczytałem w internecie na temat klas biznesowych i domenowych i znalazłem na stackoverflow, że:
-Klasa domenowa to taka klasa, która reprezentuje jakieś dane i służy do mapowania danych z bazy danych do Javowych obiektów (w Hibernate/Jpa te klasy są oznaczone @Entity).
-Klasa biznesowa, to klasa którą "wykorzystujemy" w aplikacji tzn zmapowana już klasa domenowa, na jakąś inną klasę do której została przeniesiona logika.
Druga osoba stwierdziła, że on nie widzi różnicy między klasami biznesowymi a klasami domenowymi. Więc tak na prawdę, czym są klasy biznesowe i klasy domenowe? Szerzej, co oznacza "biznesowy" (mowa o np logice biznesowej, obiektach, klasach biznesowych etc.) Czy logika biznesowa oznacza "zwykłą logikę" w połączeniu z metodami takimi jak walidacja, zabezpieczenia, dodawanie (czy inne modyfikacje) do bazy danych?

Przepraszam, za długie posty ale jeszcze jedno pytanie:
Mówiąc obiekty bazodanowe, macie na myśli obiekty przechowywane w bazie danych (np w postaci tabeli) tak? Jeżeli tak, to o ile dobrze kojarzę Hibernate sam upraszcza pola @Entity (dto) np zamiast Enuma przechowując ordinala albo zwykłego Stringa. Jeżeli klasa oadnotowana @Entity jest dto, to czy możliwe jest uproszczenie takiej klasy, żeby nie została zmieniona struktura tabel w bazie danych?

0

Więc tak na prawdę, czym są klasy biznesowe i klasy domenowe?

Ja używam tych pojęć zamiennie.

Mówiąc obiekty bazodanowe, macie na myśli obiekty przechowywane w bazie danych (np w postaci tabeli) tak?

Chodziło mi o klaski, które są odzwierciedleniem 1:1 tabel w bazie danych. Tak to się dzieje w Slicku: http://slick.lightbend.com/doc/3.2.3/schemas.html#mapped-tables

case class User(id: Option[Int], first: String, last: String)

class Users(tag: Tag) extends Table[User](tag, "users") {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
  def first = column[String]("first")
  def last = column[String]("last")
  def * = (id.?, first, last) <> (User.tupled, User.unapply)
}
val users = TableQuery[Users]

Klasą bazodanową jest tutaj User. Ja zwykle nazywam takie klasy User.Row (tzn klasa wewnętrzna Row) albo czasem UserRow po to, żeby się nie myliły z klasami biznesowymi.

W Hibernate/ JPA/ itd wygląda to na pewno zupełnie inaczej, ale w takim https://www.jooq.org/ już powinno wyglądać podobnie jak w Slicku.

1

W Hibernate/ JPA/ itd wygląda to na pewno zupełnie inaczej,

Nie bardzo. Wygląda właśnie tak ze mapuje ci tabele na obiekty ;]

-Klasa domenowa to taka klasa, która reprezentuje jakieś dane i służy do mapowania danych z bazy danych do Javowych obiektów (w Hibernate/Jpa te klasy są oznaczone @Entity).

NIE NIE NIE. Klasy domenowe czy biznesowe to są wszystkie obiekty w aplikacji, które związane są z logiką domenową tejże aplikacji. DTO, czy klasy @Entity to są po prostu "końcówki" które łączą twoja aplikację z zewnętrznymi systemami. Zwykle to są POJO/DTO/struktury danych/Value Classes. Nie ma w nich logiki.

Bzdury które powtarzasz biorą się z tego, ze wielu ludzi nie wychodzi poza pierwszy tutorial, a te zwykle nie mają w ogóle logiki biznesowej. Jak masz generycznego CRUDa, to tam NIE MA logiki, nie ma klas domenowych ani w ogóle nic nie ma. Masz jakieś przemapowanie DTO ciagniętych z bazy danych na DTO transformowane do JSONa przez jakiś kontroler webowy. Logiki tam nie ma.

0

@Aisekai: Zacznijmy może inaczej. Wzorzec DTO został zdefiniowany w ponieważ w niektórych językach nie masz struktur w rozumieniu C/Cpp. Jednocześnie przesyłanie danych w postaci mapy nie jest optymalne. W tym celu właśnie powstało DTO. Dane możesz przesyłać po sieci, ale to uproszczenie. Chodzi o przesyłanie do innego procesu/aplikacji.
Mówiąc prościej jeżeli chcesz wysłać jakąś informację do np. aplikacji w Angularze to używasz DTO. Jeżeli chcesz komunikować się z inną aplikacją w np. Ruby to wysyłasz DTO, ale żeby taka komunikacja była możliwa musicie jeszcze ustanowić wspólny protokół komunikacji. Obecnie popularny jest JSON/YAML i pchanie tego po http. Jednak kiedyś używano XMLa (soap) i pchano po http. Protokół poza sposobem transmisji (http) to też struktura danych (JSON), a DTO w tym przypadku to element interfejsu mechanizmu mapującecgo.
"Specyficznym" przypadkiem DTO są encje bazodanowe (oznaczone @Entity), bo w ich przypadku trudno rozgraniczyć elementy związane z danymi jako takimi i elementy związane z procesem translacji do formatu akceptowanego przez bazę danych. Paradoksalnie jeżeli chcesz dostosować swoje obiekty do zmian w strukturze JSONa, to też wpychasz adnotacje w rodzaju @JsonElement.

Szerzej, co oznacza "biznesowy" (mowa o np logice biznesowej, obiektach, klasach biznesowych etc.)

Mowa o regułach biznesowych, które operują na pojęciach i obiektach biznesowych, czyli encjach biznesowych. Przejrzyj https://bottega.com.pl/pdf/materialy/ddd/ddd1.pdf to cię trochę oświeci.

0

Dzięki Wszystkim za pomoc w zrozumieniu dto. Zrozumiałem też, o co chodziło Wibowitowi mówiąc o upraszczaniu dto - bardzo ciekawy pomysł. Teraz, jak przeczytałem jeszcze raz jego wypowiedź, to zorientowałem się, że wcześniej źle przeczytałem jego trzeci post.
Czyli do logiki biznesowej może zaliczać się np klasa generująca pdfa z listą zakupów (albo przepisem kulinarnym) w aplikacji kulinarnej?

0

Czyli do logiki biznesowej może zaliczać się np klasa generująca pdfa z listą zakupów (albo przepisem kulinarnym) w aplikacji kulinarnej?

Nie, bo to nie jest reguła biznesowa, a fragment interfejsu użytkownika. Do logiki biznesowej zalicza się raczej klasa, która reprezentuje operację przygotowania danych do generatora.:

+---------------+             +------------------+
|               |             |                  |              +-------------------+
|               |             |                  |              |                   |
|Żądanie Klienta+------------>+Przygotowanie     +------------->+Abstrakcyjny       |
|               |             |Danych            |              |generator wyjścia  |
|               |             |                  |              |                   |
|               |             |                  |              +-------------------+
+---------------+             +------------------+
                                        |
                                        |
                              +---------v--------+
                              |                  |
                              |Źródło danych     |
                              |                  |
                              |                  |
                              +------------------+

Tak mniej więcej. I tylko przygotowanie danych może być regułą biznesową.

0

Wydaje mi się, że i to zrozumiałem. Dzięki i w tym przypadku za pomoc.

0

Czyli do logiki biznesowej może zaliczać się np klasa generująca pdfa z listą zakupów (albo przepisem kulinarnym) w aplikacji kulinarnej?

Jw., samo generowanie pdfa to jest raczej taka transformacja jak generowanie jsona. Ale np. gdyby twoja aplikacja po podaniu przez użytkownika listy produktów generowała listę dań i przepisów, to mogła by to być logika biznesowa :)

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