Za każdym razem, gdy programista pyta „po co to robić?”, Tak naprawdę ma na myśli „nie widzę przypadku, w którym robienie tego przynosi korzyść”. W tym celu przedstawię kilka przykładów.
Wszystkie przykłady będą oparte na tym prostym modelu danych:
Person
Jednostka ma pięć właściwości:Id, FirstName, LastName, Age, CityId
Możesz założyć, że aplikacja korzysta z tych danych na wiele różnych sposobów (raporty, formularze, wyskakujące okienka ...).
Cała aplikacja już istnieje. Wszystko, o czym wspominam, to zmiana istniejącej bazy kodu. Należy o tym pamiętać.
Przykład 1 - Zmiana podstawowej struktury danych - Bez DTO
Wymagania się zmieniły. Wiek osoby musi być dynamicznie pobierany z rządowej bazy danych (załóżmy na podstawie imienia i nazwiska).
Ponieważ nie musisz już przechowywać Age
wartości lokalnie, dlatego należy ją usunąć z Person
encji. Ważne jest, aby zdać sobie sprawę, że jednostka reprezentuje dane bazy danych i nic więcej. Jeśli nie ma go w bazie danych, nie ma go w encji.
Gdy pobierzesz wiek z rządowego serwisu internetowego, będzie on przechowywany w innym obiekcie (lub int).
Ale twój interfejs nadal wyświetla wiek. Wszystkie widoki zostały skonfigurowane do korzystania z Person.Age
właściwości, która już nie istnieje. Pojawia się problem: wszystkie widoki odnoszące się do Age
osoby muszą zostać naprawione .
Przykład 2 - Zmiana podstawowej struktury danych - Z DTO
W starym systemie, istnieje również PersonDTO
jednostka z tych samych pięciu właściwości: Id, FirstName, LastName, Age, CityId
. Po pobraniu a Person
warstwa usługi przekształca ją w a, PersonDTO
a następnie zwraca.
Ale teraz wymagania się zmieniły. Wiek osoby musi być dynamicznie pobierany z rządowej bazy danych (załóżmy na podstawie imienia i nazwiska).
Ponieważ nie musisz już przechowywać Age
wartości lokalnie, dlatego należy ją usunąć z Person
encji. Ważne jest, aby zdać sobie sprawę, że jednostka reprezentuje dane bazy danych i nic więcej. Jeśli nie ma go w bazie danych, nie ma go w encji.
Jednakże, ponieważ masz pośrednika PersonDTO
, ważne jest, aby zobaczyć, że klasa ta może utrzymać się Age
nieruchomość. Warstwa usługi pobierze Person
plik, przekształci go w PersonDTO
, a następnie pobierze wiek osoby z rządowego serwisu internetowego, zapisze tę wartość PersonDTO.Age
i przekaże ten obiekt.
Ważną kwestią jest to, że każdy, kto korzysta z warstwy usług, nie widzi różnicy między starym a nowym systemem . Obejmuje to twój frontend. W starym systemie otrzymał pełny PersonDTO
obiekt. A w nowym systemie nadal otrzymuje pełny PersonDTO
obiekt. Widoki nie muszą być aktualizowane .
To mamy na myśli, gdy używamy wyrażenia rozdzielającego obawy : Istnieją dwa różne obawy (przechowywanie danych w bazie danych, prezentacja danych w interfejsie użytkownika) i każdy z nich potrzebuje innego typu danych. Nawet jeśli te dwa typy danych zawierają teraz te same dane, może się to zmienić w przyszłości.
W podanym przykładzie Age
jest różnica między dwoma typami danych: Person
(jednostka bazy danych) nie potrzebuje Age
, ale PersonDTO
(typ danych interfejsu użytkownika) jej potrzebuje.
Dzięki oddzieleniu problemów (= tworzenie oddzielnych typów danych) od początku baza kodów jest znacznie bardziej odporna na zmiany wprowadzone w modelu danych.
Możesz argumentować, że posiadanie obiektu DTO po dodaniu nowej kolumny do bazy danych oznacza, że musisz wykonać podwójną pracę, dodając właściwość zarówno do encji, jak i DTO. To technicznie poprawne. Utrzymanie dwóch klas zamiast jednej wymaga nieco więcej wysiłku.
Musisz jednak porównać wymagany wysiłek. Kiedy dodaje się jedną lub więcej nowych kolumn, kopiowanie / wklejanie kilku właściwości nie trwa tak długo. Gdy model danych zmienia się strukturalnie, konieczność zmiany frontendu, być może w sposób powodujący błędy tylko w czasie wykonywania (a nie w czasie kompilacji), wymaga znacznie więcej wysiłku i wymaga od programistów poszukiwania błędów.
Mógłbym podać więcej przykładów, ale zasada zawsze będzie taka sama.
Podsumowując
- Oddzielne obowiązki (obawy) muszą działać niezależnie od siebie. Nie powinny udostępniać żadnych zasobów, takich jak klasy danych (np.
Person
)
- To, że jednostka i jej DTO mają te same właściwości, nie oznacza, że musisz scalić je w tę samą jednostkę. Nie rób zakrętów.
- Jako bardziej rażący przykład załóżmy, że nasza baza danych zawiera kraje, piosenki i ludzi. Wszystkie te podmioty mają
Name
. Ale tylko dlatego, że wszystkie mają Name
właściwość, nie oznacza, że powinniśmy sprawić, aby dziedziczyli po wspólnej EntityWithName
klasie bazowej. Różne Name
właściwości nie mają znaczącego związku.
- Jeśli jedna z właściwości kiedykolwiek się zmieni (np. Nazwa utworu
Name
zostanie zmieniona Title
lub ktoś otrzyma znak „ FirstName
i” LastName
), będziesz musiał poświęcić więcej wysiłku, aby cofnąć dziedzictwo, którego nawet nie potrzebowałeś .
- Chociaż nie jest to tak oczywiste, twój argument, że nie potrzebujesz DTO, gdy masz byt, jest taki sam. Patrzysz na teraz , ale nie przygotowujesz się do przyszłych zmian. JEŚLI jednostka i DTO są dokładnie takie same i JEŻELI możesz zagwarantować, że nigdy nie będzie żadnych zmian w modelu danych; masz rację, że możesz pominąć DTO. Chodzi o to, że nigdy nie można zagwarantować, że model danych nigdy się nie zmieni.
- Dobra praktyka nie zawsze się opłaca. Może zacząć się opłacać w przyszłości, kiedy będziesz musiał ponownie odwiedzić starą aplikację.
- Głównym zabójcą istniejących baz kodu jest obniżanie jakości kodu, co coraz bardziej utrudnia utrzymanie bazy kodu, dopóki nie przekształci się w bezużyteczny bałagan kodu spaghetti, którego nie da się utrzymać.
- Dobra praktyka, taka jak oddzielenie problemów od dotarcia do celu, ma na celu uniknięcie tego śliskiego nachylenia złej konserwacji, aby utrzymać bazę kodów tak długo, jak to możliwe.
Jako ogólną zasadę, aby rozważyć rozdzielenie problemów, pomyśl o tym w ten sposób:
Załóżmy, że wszystkie problemy (interfejs użytkownika, baza danych, logika) są obsługiwane przez inną osobę w innym miejscu. Mogą komunikować się tylko przez e-mail.
W dobrze wyodrębnionej bazie kodu zmiana konkretnego problemu musi być obsługiwana tylko przez jedną osobę:
- Zmiana interfejsu użytkownika wymaga jedynie tworzenia interfejsu użytkownika.
- Zmiana metody przechowywania danych wymaga jedynie tworzenia bazy danych.
- Zmiana logiki biznesowej dotyczy tylko dewelopera biznesowego.
Gdyby wszyscy ci programiści korzystali z tej samej Person
jednostki, a do encji wprowadzono niewielką zmianę, wszyscy musieliby być zaangażowani w proces.
Ale dzięki zastosowaniu osobnych klas danych dla każdej warstwy problem ten nie jest tak powszechny:
- Tak długo, jak deweloper bazy danych może zwrócić prawidłowy
PersonDTO
obiekt, deweloper biznesowy i interfejs użytkownika nie przejmują się tym, że zmienił sposób przechowywania / pobierania danych.
- Tak długo, jak deweloper biznesowy przechowuje dane w bazie danych i dostarcza niezbędnych danych do interfejsu użytkownika, deweloperzy bazy danych i interfejsu użytkownika nie dbają o to, czy zdecyduje się przerobić swoje reguły biznesowe.
- Tak długo, jak interfejs użytkownika można zaprojektować w oparciu o `PersonViewModel, deweloper interfejsu użytkownika może zbudować interfejs użytkownika, jak tylko chce. Deweloperzy baz danych i biznesowi nie dbają o to, jak to się robi, ponieważ nie ma na nie wpływu.
Kluczowym zwrotem jest to, że nie ma na nie wpływu . Wdrożenie dobrego rozdziału problemów ma na celu zminimalizowanie wpływu na inne strony (a zatem konieczność zaangażowania).
Oczywiście, niektóre poważne zmiany nie mogą uniknąć włączenia więcej niż jednej osoby, np. Kiedy do bazy danych dodawany jest zupełnie nowy byt. Ale nie należy lekceważyć ilości drobnych zmian, które należy wprowadzić w trakcie życia aplikacji. Poważne zmiany to mniejszość liczbowa.
What's the benefit of these conversions?
oddzielenie modelu danych trwałości od modelu danych (reprezentacji) oferowanego konsumentom. Korzyści z oddzielania płatności zostały szeroko omówione w SE Jednak celem pod DTO jest zebranie w jednej odpowiedzi tylu informacji, ile uzna za konieczne, aby klienci mogli zapisać połączenia z serwerem. Co sprawia, że komunikacja klient-serwer jest płynniejsza.