Czy DTO maja sens? Załóżmy, że mamy kilka DTO - do tworzenia encji, do updatu i do wyświetlania. 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, nie mówiąc, że konstruktory tych wszystkich obiektów mogą być użyte w jeszcze większej ilości miejsc. Jakby do tego dodać jeszcze oddzielanie encji biznesnowych od encji JPA to robi się kolejne miejsce, które trzeba zmodyfikować. 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.
DTO się używa jak masz różnice w modelach. Na przykład jak zwracasz User to nie chcesz zwrócić całej klasy tylko informacje wyświetlane w profilu. Jeśli masz robić 1 do 1 to faktycznie DTO nie ma sensu.
Nie wygląda to za ciekawie
Odnoszę wrażenie, że zakładasz, że masz tylko 2 wyjścia:
- wszędzie używać po kilka DTO (typu UserDTO, CreateUserDTO, UpdateUserDTO ...) i faktycznie dodanie nowego pola często wymaga kilkudziesięciu zmian i pisania mapperów do mapperów
- używać wyłącznie klasy domenowej typu User (będącej jak mniemam encją JPA, bo coś nią musi być, a mamy tylko 1 "główny obiekt" jak mówisz), w której jest wszystko, czego może ktokolwiek kiedykolwiek potrzebować - co powoduje, że np. JWT zawiera grupę krwi użytkownika
A może by tak wypośrodkować? Tam gdzie jest sens to rozdzielać to rozdzielać, a tam gdzie nie to nie?
DTO zazwyczaj nie mają sensu na samym początku, bo wszystkie modele są projektowane pod ten sam use case, dopiero potem pojawia się rozjazd albo więcej przypadków użycia tego samego kodu
Moja rada: robić według własnego uznania. IMO najważniejsze są testy na poziomie aplikacji, wtedy w kodzie możesz mieć burdel, który tak czy owak jest prosty do zrefaktowania, gdy jest to konieczne
Nofenak napisał(a):
Jeśli chcemy dodać jakieś pole, czy je usunąć czy zmienić to musimy to zrobić zazwyczaj w tych 3 DTO
Jeśli tak, to może to być sygnał za tym, że jednak żadnej abstrakcji nie ma; a więc te DTO są nie potrzebne.
Jaki jest sens mieć 3x DTO które mają te same dane? Żaden.
PS: Takie coś może powstać, jak ktoś się chce upierać na jakieś arbitralne zasady, że "w miejscu X zawsze musi być DTO".
Ja wychodzę z założenia, że encja (w rozumieniu JPA, co często jak wiadomo zresztą, jest 1-1 z domeną) to taki trochę gorący ziemniak, z masą overheadu, takim jak dirty checking, detached state, n+1 itd. Używasz go tylko tam gdzie potrzebujesz i masz to pod kontrolą, a w innych miejscach zwracasz DTO. Czasami to imo bardziej projekcja niż DTO ale czasami też, nie ma sensu tych definicji rozgraniczać. Mówię to wszystko np. w kontekście niektórych antypatternów jak open session view itd. Kilka DTO może nie zawsze mieć sens ale jedno ma sens prawie zawsze.
Nofenak napisał(a):
Czy DTO maja sens?
Tak, jak najbardziej.
Równie dobrze mógłbyś spytać, czy wiertarki udarowe mają sens. Narzędzia to narzędzia, różne się przydają.
Załóżmy, że mamy kilka DTO - do tworzenia encji, do updatu i do wyświetlania. 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, nie mówiąc, że konstruktory tych wszystkich obiektów mogą być użyte w jeszcze większej ilości miejsc. Jakby do tego dodać jeszcze oddzielanie encji biznesnowych od encji JPA to robi się kolejne miejsce, które trzeba zmodyfikować. Nie wygląda to za ciekawie.
Czyli problem polega na tym, że jak jest robota do zrobienia, to trzeba coś zrobić?
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.
I w efekcie przypadkowo wyświetlać wartości, których nie powinno się wyświetlać, albo transferować zbędne dane?
Czyli problem polega na tym, że jak jest robota do zrobienia, to trzeba coś zrobić?
Nie, pytam czy ta robota ma jakiś sens.
I w efekcie przypadkowo wyświetlać wartości, których nie powinno się wyświetlać, albo transferować zbędne dane?
No dobra, a co jeśli mamy jakiś obiekt np. z 20 polami i jakiś 2 wrażliwych nie chcemy wysyłać? Będziemy tworzyć DTO z 18 polami specjalnie dla tych tylko 2 pól i potem utrzymywać te 2 osobne obiekty i ich mappery? Nie lepiej ten główny sparsować do JSON-a, usunąć te 2 pola i już? W obecnym projekcie spotkałem się z takim rozwiązaniem i trochę mnie ono zaskoczyło, bo byłem przyzwyczajony właśnie do DTOsów, ale wydaje się działać sensownie. A z kolei w poprzednim projekcie miałem kilka różnych DTO - request z controllera, potem ten request był mappowany na commende, z commendy pobierało się dane i tworzyło się główny obiekt, a potem ten obiekt mappowało się jeszcze na encje JPA no i jeszcze DTO do wyświetlania. a te obiekty były praktycznie takie same. Oczywiście wszystko w różnych, często zagnieżdżonych katalogach, więc dodanie 1 nowego pola było absurdalnie uciążliwe i rozbuchane.
Nofenak napisał(a):
No dobra, a co jeśli mamy jakiś obiekt np. z 20 polami i jakiś 2 wrażliwych nie chcemy wysyłać? Będziemy tworzyć DTO z 18 polami specjalnie dla tych tylko 2 pól i potem utrzymywać te 2 osobne obiekty i ich mappery? Nie lepiej ten główny sparsować do JSON-a, usunąć te 2 pola i już? W obecnym projekcie spotkałem się z takim rozwiązaniem i trochę mnie ono zaskoczyło, bo byłem przyzwyczajony właśnie do DTOsów, ale wydaje się działać sensownie. A z kolei w poprzednim projekcie miałem kilka różnych DTO - request z controllera, potem ten request był mappowany na commende, z commendy pobierało się dane i tworzyło się główny obiekt, a potem ten obiekt mappowało się jeszcze na encje JPA no i jeszcze DTO do wyświetlania. a te obiekty były praktycznie takie same. Oczywiście wszystko w różnych, często zagnieżdżonych katalogach, więc dodanie 1 nowego pola było absurdalnie uciążliwe i rozbuchane.
DTO to jest szczegół implementacyjny. Jeśli chcesz ukrywać jakieś dane, to napisz sobie test który wsadza dane na wejściu, i robi asercję że nie ma ich na wyjściu. A to czy zaimplementujesz to DTOsem, mapą, słownikiem, listą czy czymkolwiek innym nie ma znaczenia.
Nofenak napisał(a):
A z kolei w poprzednim projekcie miałem kilka różnych DTO - request z controllera, potem ten request był mappowany na commende, z commendy pobierało się dane i tworzyło się główny obiekt, a potem ten obiekt mappowało się jeszcze na encje JPA no i jeszcze DTO do wyświetlania. a te obiekty były praktycznie takie same. Oczywiście wszystko w różnych, często zagnieżdżonych katalogach, więc dodanie 1 nowego pola było absurdalnie uciążliwe i rozbuchane.
A może by tak nie iść ze skrajności w skrajność i wypośrodkować? U mnie zwykle komendy (bo lubię CQRS) to DTOsy czy tam Requesty i flow wtedy się dużo upraszcza bo masz przykładowo
public class CreateUser
{
public string Name { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
public class CreateUserHandler
{
public async Task Handle(CreateUser command)
{
// logika
}
}
public class UpdateUserAge
{
public int Id { get; set; }
public int Age { get; set; }
}
public class UpdateUserAgeHandler
{
public async Task Handle(UpdateUserAge command)
{
// logika
}
}
public class UserController
{
// ...
public async Task<IActionResult> New(CreateUser command)
{
await _mediator.Send(command);
// ...
}
public async Task<IActionResult> UpdateAge(UpdateUserAge command)
{
await _mediator.Send(command);
// ...
}
}
Ogólnie - czasem podział na DTO/obiekt biznesowy/encje można olać, ale argument w stylu "trzeba utrzymywać konwersję" jest dla mnie z duszy. W 80% przypadkach wyklepanie konwertera to 20 minut roboty, 40 minut z testami, a obecnie z jakimś GPT to 5 minut. Jeśli DTO i np. encja pozostaną na zawsze takie same to jest to zabieg jednorazowy, ale jeśli DTO i encja będą różne - to okaże się, że podział ten ma sens.
Korzyścią istnienia obiektu dla widoku, obiektu biznesowego i encji jest jedno: że mamy separację odpowiedzialności. Gdy masz jeden obiekt to będzie miał w sobie:
- potencjalną logikę biznesową
- logikę związaną z serializacją
- logikę związaną z bazą danych
Np.
class MyEntity {
@PrimaryKey
@Column
private Long id;
@Column
@JsonFormat(format = "yyyy-MM-dd hh:mm:ss")
private Timestamp timestamp;
// getters, setters
public boolean isBetween(Timestamp start, Timestamp end) {
// logic
}
}
Dla mnie jest to pomieszanie z poplątaniem, wolę mieć jednak trzy klasy niż jedną. Oczywiście mowa tutaj o "standardowym" scenariuszu, nie o scenariuszu gdy walczy się o cykle.
DTO jak sama nazwa wskazuje służy do komunikacji / transferu danych pomiędzy systemami czyli jakiś kawałek domeny pakujesz w worek i serializujesz / deserializujesz.
DTOsy powinny być traktowane bardziej jako struktura danych niż obiekt z logiką. Warto mieć te warstwy odseparowane, czasem w domenie rzeczy są reprezentowane zupełnie inaczej niż to jak są przesyłane lub zapisywane.
Nofenak napisał(a):
Nie, pytam czy ta robota ma jakiś sens.
Jeśli chcemy osiągnąć jakiś cel, to zazwyczaj musimy coś zrobić. Zazwyczaj lepiej zrobić coś prostego niż kombinować - bo to się później mści.
No dobra, a co jeśli mamy jakiś obiekt np. z 20 polami i jakiś 2 wrażliwych nie chcemy wysyłać? Będziemy tworzyć DTO z 18 polami specjalnie dla tych tylko 2 pól i potem utrzymywać te 2 osobne obiekty i ich mappery?
DTO ma spełniać swój cel, a nie być tworzony specjalnie.
Zazwyczaj inne dane są potrzebne do tworzenia obiektu, inne do aktualizacji, jeszcze inne do wyświetlenia. To są trzy różne przypadki użycia, więc zgodnie z zasadą pojedynczej odpowiedzialności potrzebujemy oddzielnego kodu do ich obsłużenia.
Wątku utrzymywania mapperów nie rozumiem - wydaje mi się, że w nowoczesnych językach one albo działają automatycznie, albo są generowane w locie.
Nie lepiej ten główny sparsować do JSON-a, usunąć te 2 pola i już?
WTF, uzależnienie działania logiki prezentacji od jakiegoś konkretnego formatu danych?
A z kolei w poprzednim projekcie miałem kilka różnych DTO - request z controllera, potem ten request był mappowany na commende, z commendy pobierało się dane i tworzyło się główny obiekt, a potem ten obiekt mappowało się jeszcze na encje JPA no i jeszcze DTO do wyświetlania. a te obiekty były praktycznie takie same. Oczywiście wszystko w różnych, często zagnieżdżonych katalogach, więc dodanie 1 nowego pola było absurdalnie uciążliwe i rozbuchane.
Ok, to brzmi jak jakaś patoarchitektura, i tak - zgadzam się, że tyle obiektów i mapowania nie ma sensu. Ale to przez warstwy, a nie przez to, że wyświetlanie i zapisywanie do bazy miały oddzielne DTO, bo to jest akurat prawidłowe.
Ja tam jestem nauczony, że takiej surowej encji się nie pcha pomiędzy różnymi warstwami abstrakcji w oprogramowaniu tylko właśnie po otrzymaniu responsu z bazy po jakimś query w repozytorium mapuje to do DTO i dopiero to DTO z jedynie potrzebnymi danymi pcham dalej.
markone_dev napisał(a):
Nofenak napisał(a):
A z kolei w poprzednim projekcie miałem kilka różnych DTO - request z controllera, potem ten request był mappowany na commende, z commendy pobierało się dane i tworzyło się główny obiekt, a potem ten obiekt mappowało się jeszcze na encje JPA no i jeszcze DTO do wyświetlania. a te obiekty były praktycznie takie same. Oczywiście wszystko w różnych, często zagnieżdżonych katalogach, więc dodanie 1 nowego pola było absurdalnie uciążliwe i rozbuchane.
A może by tak nie iść ze skrajności w skrajność i wypośrodkować? U mnie zwykle komendy (bo lubię CQRS) to DTOsy czy tam Requesty i flow wtedy się dużo upraszcza bo masz przykładowo
public class CreateUser { public string Name { get; set; } public string Email { get; set; } public int Age { get; set; } } public class CreateUserHandler { public async Task Handle(CreateUser command) { // logika } } public class UpdateUserAge { public int Id { get; set; } public int Age { get; set; } } public class UpdateUserAgeHandler { public async Task Handle(UpdateUserAge command) { // logika } } public class UserController { // ... public async Task<IActionResult> New(CreateUser command) { await _mediator.Send(command); // ... } public async Task<IActionResult> UpdateAge(UpdateUserAge command) { await _mediator.Send(command); // ... } }
Tylko teoretycznie się upraszcza. W praktyce jak masz endpoint, który ma w urlu Id updateowanego rekordu to średnio to pasuje. Albo musisz mieć do body osobny dtos albo jakoś go w kontrolerze wsadzić do komendy albo jakiś swój model binder czy jak tam się to zwie
Jeśli ktoś nie widzi sensu DTO to pewnie jest głupcem, albo nie nadaje się na swoje stanowisko
Oczywiście to z wiadomej baśni i ironicznie.
DTO to jest rozwiązanie sensowne w niektórych przypadkach.
Ale generalnie większość DTO jest bez sensu - służy matołkom architektonicznym, którzy nie ogarniają, że MOŻNA stosować różne rozwiązania w różnych miejscach tego samego projektu.
Matołki muszą mieć wszystko na jedno kopyto zrobione, bo inaczej gubią się.
I jeśli w 10% przypadków przydaje się jakies DTO, to w takim razie musi być używane wszędzie bo przecież w innych miejscach nie przeszkadza, a na zapas zawsze się przydać może.
(Jakby tak robić auta - to każdy samochód musiałby mieć 40 miejsc, żeby w razie czego przewieźć wycieczke szkolną, składanych, żeby przewieźć ziemniaki, i mieć pompę plus możliwość uszczelnienia, gdyby była potrzeba wywiezienia szamba).
Dramat potęgują różnorakie mappery, które ułatwiają robienie takich bezsensownych mapowań 1 do 1. Niestety, przynajmniej w javie IDE nie nadążają za mapperami, (które przeważnie bazują na refleksji) i mamy dramaty typu : kto do cholery ustawia to pole na 42???, przecież to nigdzie nie jest wołane
.
Osobnym problemem jest JPA, które jak używasz DTO i mapperów jest mega problematyczne, a jak nie używasz to jest mega problematyczne i niebezpieczne.
(np. przy przesyłaniu "na front" zerujemy pola, których nie chcemy pokazać użytkownikowi ... i magicznie pola zerują się również w bazie).
jarekr000000 napisał(a):
(np. przy przesyłaniu "na front" zerujemy pola, których nie chcemy pokazać użytkownikowi ... i magicznie pola zerują się również w bazie).
I to jest właśnie przykład problemu wynikający z braku DTO i "oszczędzeniu" sobie roboty poprzez utrzymywanie tylko jednej klasy.
somekind napisał(a):
jarekr000000 napisał(a):
(np. przy przesyłaniu "na front" zerujemy pola, których nie chcemy pokazać użytkownikowi ... i magicznie pola zerują się również w bazie).
I to jest właśnie przykład problemu wynikający z braku DTO i "oszczędzeniu" sobie roboty poprzez utrzymywanie tylko jednej klasy.
To jest przykład debilizmu niektórych ORM. (przy czym tylko jeden z wielu). JPA jest naprawdę powalone - nawet w kategorii javowych frameworków, a to wysoko postawiona poprzeczka.
Nofenak napisał(a):
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, nie mówiąc, że konstruktory tych wszystkich obiektów mogą być użyte w jeszcze większej ilości miejsc.
No to raczej w drugą stronę powinno być - chcesz dodać pole w bazie to dodajesz w jednym miejscu, nie musisz się martwić że dane przypadkowo z automatu trafią do responsa na front i nie musisz nic zmieniać. Dopiero jak chcesz wykorzystać to pole w jakimś miejscu to musisz dodać jego obsługę, np gdy chcesz wyświetlić je na jakiejś stronie to w modelu tego widoku dodajesz to pole i to jest niby dodatkowa robota ale przecież i tak musisz ustalić gdzie to pole musi się znajdować i zachowywać więc to najmniejszy problem i nie robisz tego wszędzie tylko tam gdzie potrzebujesz.
No chyba że masz prostego CRUDa który tak naprawdę tylko pomaga edytować tabelki w bazie i wyświetla wszystko co może - pracowałem w takim projekcie gdzie bardzo często dodawaliśmy pola a UI musiał to pokazać po prostu jako dodatkową linijkę i pozwolić na edycję i sobie tak przepychaliśmy to pole przez 5 DTO, mapperów, serwisów itp i za każdym razem trzeba było to pole dodać w 10 nowych miejscach bo taka była "architektura". Trzeba sobie zadać w tym przypadku pytanie jaki jest sens rodzielać model skoro tak naprawdę edytujemy 1:1 dane w bazie SQL.
jarekr000000 napisał(a):
(np. przy przesyłaniu "na front" zerujemy pola, których nie chcemy pokazać użytkownikowi ... i magicznie pola zerują się również w bazie).
zerowanie pól których nie chcemy pokazać użytkownikowi brzmi jak niezła prowizorka
szydlak napisał(a):
Tylko teoretycznie się upraszcza. W praktyce jak masz endpoint, który ma w urlu Id updateowanego rekordu to średnio to pasuje. Albo musisz mieć do body osobny dtos albo jakoś go w kontrolerze wsadzić do komendy albo jakiś swój model binder czy jak tam się to zwie
CQRS robiony za pomocą MediatoR (przykład z update usera przez admina ):
public class UpdateUseCommand : AdminRequest, IRequest<Either<Failure, UpdateUseCommand.Result>>
{
public Guid Id {get; set;}
public string FirstName { get; }
public string LastName { get; }
public string Email { get; }
public class Result
{
public EmailStatus EmailStatus { get; set; }
}
}
public class UpdateUseCommand : IRequestHandler<UpdateUseCommand, Either<Failure, UpdateUseCommand.Result>>
{
public async Task<Either<Failure, UpdateUseCommand.Result>> Handle(UpdateUseCommand command, CancellationToken cancellationToken)
{
<Logika>
}
}
[HttpPut]
[Route("/user/{id:guid}")]
public async Task<IActionResult> UpdateUserDetails(Guid id, [FromBody] UpdateUseCommand command)
{
if (command == null) return BadRequest();
command.Id = id;
return ToHttpResult(await _mediator.Send(command, HttpContext.RequestAborted));
}
W Result zwracanymamy ViewModel, w samym UpdateUseCommand.Result mamy ViewModel, który dostajemy od usera, w kontrolerze zaś przypisujemy Id z url.
I tak naprawdę to praktycznie nie korzystamy z DTO(w paru miejscach mamy, ale względu na korzystanie z monad i wygodniej przekazuje się dalej jeden obiekt niż kilka).
Do tego dochodzi jeszcze kolejna klasa do validatora.
I imho to podejście mi się bardzo podoba, może i samego kodu trzeba więcej napisać, ale samo utrzymanie jest już łatwe. Dodatkowo jak ktoś nowy musi wejść, to również ma łatwiej.
Plus nie ma tych okropnych klas od serwisów po +1000 linii kodu
AdminRequest
jest wykorzystywany do pipelinów, akurat w tym przypadku autoryzacja admina, określenie tenantu do jakiego jest przypisany i troszkę innych rzeczy pomocnych w analizie logów.
EDIT:
Co do pytanie czy DTO(oraz VM) mają sens? Mają, nie wyobrażam sobie robienia czegoś takiego jak encja na twarz i pchasz. Dany endpoint ma mieć tylko potrzebne pola i tyle
Dto mają sens, nieużywanie dto nie ma sensu. Zakładam, że mówimy o typowej aplikacji webowej, bo w takim kontekscie zazwyczaj się mówi o DTOsach.
Zacznijmy od definicji, DTO rozumiem jako obiekt do transferu danych, bez logiki, łatwo serializowalny.
W praktyce używamy np. jako request body lub request response w http, używany jako reprezentacja eventu do wrzucenia na kolejkę etc.
Weźmy jakąś przyziemną domenę np. to forum. Mamy wątki i posty (thread/post). Post w bazie danych oprócz oczywistych danych, które jako użytkownik możemy edytować - content
, będzie miał pola nieedytowalne przez użytkownika: id autora
, timestamp utworzenia
, timestamp ostatniej edycji
, w przypadku nierelacyjnej bazy danych bez transakcji pewnie by się przydało jakieś version
do optimistic locków. No i jak to chcesz obrobić bez DTOsa typu CreatePostDto
/ UpdatePostDto
? Przecież w praktyce taki DTO będzie miał pole content
i może coś jeszcze, a baza danych jak widać kilka razy tyle pól dla posta.
Idziemy dalej - odczyt. Na stronie głównej masz trendujące wątki - jest to nazwa wątku + timestamp ostatniego posta + liczba wyświetleń. Jak chcesz to bez DTOsa oporządzić? Wypluć userowi całe rekordy z tabeli threads? Najlepiej z dojoinowanymi autorami postów i ich (mam nadzieję zahashowanymi) hasłami :D
Oh wait, liczbę wyświetleń też trzeba z czymś zjoinować, bo pewnie nie zapisuje się w rekordach w tabelce threads tylko w jakiejś innej tabeli i mamy naturalnie DTOsa typu liczba array{viewCount, thread}.
I mowa tu o absolutnie prostackiej domenie czyli pisanie postów na forum. Przy bardziej złożonej domenie widać tą potrzebę jeszcze bardziej.
Co jak mamy zdefiniowane zewnętrzne API - dzisiaj zrobiliśmy sobie bazę danych 1:1 z tym api, nagle przyjdzie zmiana api i zonk, nasza baza się nie pokrywa z nowym api.
Ogólnie plusy DTOsów:
- dają elastyczność - pozwalają modelować dane niezależnie od persystencji - daje to lepiej zdefiniowany input, bardziej przejrzysty output.
- ukrywać dane, które nie powinny wypłynąć
- zmniejszać ilość przesyłanych danych - na prawdę nie potrzebujemy całego zrzutu bazy danych przesyłać na front żeby zrobić ranking trendujących wątków.
- tworzyć łatwe kontrakty - znajdujesz endpoint to od razu widzisz co przyjmuje i zwraca i nie musisz śledzić całego kodu żeby stwierdzić jakie dane są pobierane z inputu albo co jest faktycznie wypluwane przez output.
minusy:
- trzeba pisać kod
Przypadki, gdzie można zaakceptować brak dto:
- nakładka na bazę danych - spoiler, taka aplikacja nie ma sensu i nie jest nikomu potrzebna.
- wewnętrzny PoC, gdzie zrzucenie całej bazy danych na front nie stanowi żadnego problemu.
Nie wyobrażam sobie grzebać w gównie, w którym np, encje JPA byłyby pchane po REST. No chyba że za podwójną stawkę.
Jak chcesz to bez DTOsa oporządzić? Wypluć userowi całe rekordy z tabeli threads? Najlepiej z dojoinowanymi autorami postów i ich (mam nadzieję zahashowanymi) hasłami
Czekaj no.. o ile wiele z tego co napisałeś brzmi sensownie, ale z tym się zgodzić nie mogę.
Czemu CAŁE rekordy? Przecież nie musisz robić SELECT * FROM...
tylko SELECT tresc, data, autor FROM...
i dostajesz tylko to, co jest potrzebnedo wykonania konkretnej akcji - np. wyświetlenia posta/wątku. Jest ryzyko, że ktoś może zrobić SELECT *
i jakieś dane (jak to podane hasło), które powinny być ukryte - wyciekną. Ale przecież można stworzyć widoki, które zawierają jedynie istotne kolumny dla danego scenariusza, a ponadto - ukrywają dane niejawne (w sensie - ich nie zwracają).
cerrato napisał(a):
Czemu CAŁE rekordy? Przecież nie musisz robić
SELECT * FROM...
tylkoSELECT tresc, data, autor FROM...
No i trzeba je gdzieś te dane włożyć i tym samym otrzymać DTO? Jak nazwiesz DTO widokiem, viewmodelem, commandem czy jakkolwiek inaczej to nadal idea jest ta sama.
A idea tego wątku jest taka, że po co DTOsy skoro można używać 1 modelu
Jak nazwiesz DTO widokiem, viewmodelem, commandem czy jakkolwiek inaczej to nadal idea jest ta sama.
No nie do końca - dla mnie różnica jest zasadnicza:
- jeśli aplikacja decyduje co ma zrobić/jakie dane pobrać, a potem w jakikolwiek sposób w jej wnętrzu wybiera dane, na których operuje - czy to przed wysłanie zapytania (wskazanie zwracanych kolumn przez SELECT) czy po (wrzucamy
SELECT *
i potem sobie to parsujemy/wybieramy interesujące elementy) to w razie złej woli programisty albo błędu - może narobić zamieszania - jeśli stworzymy WIDOK w bazie to programista aplikacji, chociażby się mega mocno starał, nie da rady niczego popsuć poza to, co jest objęte zakresem widoku
I nie chodzi mi o kłócenie się teraz o nazwy albo o to, który sposób jest lepszy.
Jak napisałem w poprzednim poście - dużo z tego co napisałeś jest słuszne, sensowne, rozumiem i popieram. Ale nie pasowało mi argumentowanie że trzeba/nie można z czegoś korzystać, żeby ukryć pewne kolumny i że się nie da tego zrobić bez korzystania z DTO.
Ale widok w bazie tez definiuje programista. Co to za roznica czy ten sam programista zdefiniuje ten widok w sql-u czy w aplikacji?
stivens napisał(a):
Ale widok w bazie tez definiuje programista. Co to za roznica czy ten sam programista zdefiniuje ten widok w sql-u czy w aplikacji?
Oczywiście, ale jednak nie zawsze ten sam programista ogarnia wszystko. Kilka kwestii do rozważenia:
- inny programista odpowiada za bazę, inny za aplikację
- zlecasz część aplikacji innej firmie/komuś innemu i chcesz ograniczyć ryzyko wycieku albo błędu
- baza jest dostępna dla różnych innych podmiotów - coś na zasadzie wystawienia API
- masz różne ekipy, które zajmują się różnymi składnikami/elementami apki i niekoniecznie koleś od frontu, który wykona sobie jakieś zapytanie (chociażby wspomniana jako przykład obsługa forum dyskusyjnego) nie musi dostawać ID posta w bazie czy timestampów, on potrzebuje tylko treść posta, datę umieszczenia (która nie jest timestampem w bazie) czy autora wpisu
Jest też kwestia performance'u - przecież obiekty DTO trzeba skopiować w pamięci, większość normalnych ORMów implementuje lazy loading, DTO kompletnie zwalczają ten mechanizm.
BTW: NoSQL jeszcze dycha?
loza_prowizoryczna napisał(a):
Jest też kwestia performance'u - przecież obiekty DTO trzeba skopiować w pamięci, większość normalnych ORMów implementuje lazy loading, DTO kompletnie zwalczają ten mechanizm.
A to dopiero początek ich zalet.
BTW: NoSQL jeszcze dycha?
Dobry troll. xD
To mówicie że zwracacie encję z ORMa która teraz zawiera 10 pól a za pół roku będzie zawierać 15?
Jeżeli robisz jakiegoś prostego CRUD'a, traktowanego dosłownie to używanie DTO nie ma żadnego sensu. Jak się rozrośnie, to można je niewielkim kosztem dorobić. Jeżeli system ma jakąś bardziej rozbudowaną logikę, to DTO zaczynają mieć sens, bo definiują zewnętrzny interface, który prawdopodobnie powinien być dość ściśle kontrolowany, a nie musi się zgadzać ze schematem w bazie danych.
Inny przykład na szybko - jeżeli zaczynasz budowę jakiegoś modułu od zdefiniowania API, to siłą rzeczy definicje DTO powstaną zanim zaczną być do czegokolwiek używane.
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.