Щоразу, коли розробник запитує "який сенс робити це?", Що вони насправді мають на увазі: "Я не бачу жодного випадку використання, коли це робить користь". З цією метою дозвольте показати кілька прикладів.
Усі приклади будуть засновані на цій простій моделі даних:
Суб'єкт Person
господарювання має п'ять властивостей:Id, FirstName, LastName, Age, CityId
І ви можете припустити, що програма використовує ці дані різними способами (звіти, форми, спливаючі вікна, ...).
Уся програма вже існує. Все, що я згадую, - це зміна існуючої бази даних. Це важливо пам’ятати.
Приклад 1 - Зміна основної структури даних - Без DTO
Вимоги змінилися. Вік людини потрібно динамічно виводити з урядової бази даних (припустимо, виходячи з їх імені та прізвища).
Оскільки вам більше не потрібно зберігати Age
значення локально, тому його потрібно видалити з Person
сутності. Тут важливо усвідомити, що сутність представляє дані бази даних , і нічого більше. Якщо його немає в базі даних, це не в об'єкті.
Коли ви отримаєте вік із веб-служби уряду, він буде зберігатися в іншому об'єкті (або int).
Але ваш інтерфейс все ще відображає вік. Усі представлення даних створені для використання Person.Age
ресурсу, якого зараз більше немає. Проблема представляється: усі погляди, які стосуються Age
людини, потрібно виправити .
Приклад 2 - Зміна основної структури даних - За допомогою DTO
У старій системі, є також PersonDTO
об'єкт з тими ж п'ятьма властивостями: Id, FirstName, LastName, Age, CityId
. Після отримання a Person
, сервісний рівень перетворює його в a, PersonDTO
а потім повертає.
Але зараз вимоги змінилися. Вік людини потрібно динамічно виводити з урядової бази даних (припустимо, виходячи з їх імені та прізвища).
Оскільки вам більше не потрібно зберігати Age
значення локально, тому його потрібно видалити з Person
сутності. Тут важливо усвідомити, що сутність представляє дані бази даних , і нічого більше. Якщо його немає в базі даних, це не в об'єкті.
Однак, оскільки у вас є посередник PersonDTO
, важливо бачити , що цей клас може тримати в Age
власності. Службовий рівень отримає Person
, перетворить його в а PersonDTO
, потім також вибере вік людини з веб-служби уряду, збереже це значення в PersonDTO.Age
і передасть цей об’єкт.
Важлива частина тут полягає в тому, що кожен, хто використовує сервісний рівень, не бачить різниці між старою та новою системою . Сюди входить ваш інтерфейс. У старій системі вона отримала повний PersonDTO
об’єкт. А в новій системі вона все одно отримує повний PersonDTO
об’єкт. Погляди не потрібно оновлювати .
Це те, що ми маємо на увазі, коли ми використовуємо фразове розділення проблем : Є дві різні проблеми (зберігання даних у базі даних, подання даних у передній частині), і для них потрібен різний тип даних. Навіть якщо ці два типи даних зараз містять однакові дані, це може змінитися в майбутньому.
У наведеному прикладі Age
є різниця між двома типами даних: Person
(об'єкт бази даних) не потребує Age
, але PersonDTO
(тип даних інтерфейсу) він потребує.
Виділяючи проблеми (= створення окремих типів даних) від початку, база коду набагато стійкіша до змін, внесених до моделі даних.
Ви можете стверджувати, що наявність об’єкта DTO, коли до бази даних додається новий стовпець, означає, що вам доведеться виконати подвійну роботу, додавши властивість як в об'єкті, так і в DTO. Це технічно правильно. Для підтримки двох класів замість одного потрібно трохи додаткових зусиль.
Однак вам потрібно порівняти необхідні зусилля. Коли додано один або кілька нових стовпців, копіювання / вставлення декількох властивостей не займе так довго. Коли модель даних структурно змінюється, потрібно змінити інтерфейс, можливо, таким чином, що викликає помилки лише під час виконання (а не під час компіляції), зажадає значно більше зусиль, і це вимагає від розробника (ів) піти на полювання на помилок.
Я можу навести більше прикладів, але принцип завжди буде однаковим.
Узагальнити
- Окремі обов'язки (проблеми) повинні працювати окремо один від одного. Вони не повинні ділитися будь-якими ресурсами, такими як класи даних (наприклад
Person
)
- Тільки тому, що сутність та його DTO мають однакові властивості, не означає, що вам потрібно об'єднати їх в одне ціле. Не обрізайте кути.
- Скажімо, більш чіткий приклад, скажімо, наша база даних містить країни, пісні та людей. Всі ці структури мають
Name
. Але те, що всі вони мають Name
властивість, не означає, що ми повинні змусити їх успадковувати спільний EntityWithName
базовий клас. Різні Name
властивості не мають значущого відношення.
- Якщо одна з властивостей коли-небудь зміниться (наприклад, пісня
Name
буде перейменована на Title
людину або людина отримає FirstName
і LastName
), вам доведеться витратити більше зусиль, щоб скасувати спадщину, яке вам навіть не потрібно було в першу чергу .
- Хоча це не настільки очевидно, але ваш аргумент того, що вам не потрібен DTO, коли у вас є сутність, такий же. Ви дивитесь на зараз , але не готуєтесь до будь-яких майбутніх змін. ЯКЩО сутність та DTO абсолютно однакові, А якщо Ви можете гарантувати, що жодної зміни в моделі даних ніколи не буде; то ви правильні, що можете пропустити DTO. Але річ у тому, що ви ніколи не можете гарантувати, що модель даних ніколи не зміниться.
- Належна практика не завжди окупається одразу. Це може почати окупатися в майбутньому, коли вам потрібно переглянути стару програму.
- Основним вбивцею існуючих баз коду є те, що якість коду падає, постійно ускладнюючи підтримку бази коду, поки вона не перетвориться на марний безлад коду спагетті, який неможливо досягти.
- Хороша практика, така як реалізація відокремлення проблем від місця доїзду, спрямована на те, щоб уникнути слизького схилу поганого обслуговування, щоб зберегти базу даних коду якомога довше.
Як правило, слід розглянути питання про розмежування проблем, подумайте про це так:
Припустимо, що всі проблеми (інтерфейс користувача, база даних, логіка) розглядаються різними особами в іншому місці. Вони можуть спілкуватися лише електронною поштою.
У добре відокремленій кодовій базі змін до конкретної проблеми потребує лише одна особа:
- Зміна користувальницького інтерфейсу передбачає лише розробник інтерфейсу користувача.
- Зміна методу зберігання даних передбачає лише розробник бази даних.
- Зміна логіки бізнесу передбачає лише розробку бізнесу.
Якби всі ці розробники використовували одне Person
ціле об'єднання, і в суті було внесено незначну зміну, всі повинні були б бути залучені до процесу.
Але, використовуючи окремі класи даних для кожного шару, ця проблема не є такою поширеною:
- Поки розробник бази даних може повернути дійсний
PersonDTO
об'єкт, бізнес та розробник інтерфейсу не переймаються тим, чи змінив він спосіб зберігання / отримання даних.
- Поки бізнес-розробник зберігає дані в базі даних і надає необхідні дані в інтерфейс, базу даних та розробники інтерфейсу користувача не хвилює, чи вирішив він переробити свої бізнес-правила.
- Поки інтерфейс користувача може бути розроблений на основі "PersonViewModel", тоді розробник інтерфейсу може будувати інтерфейс користувача, як вони хочуть. База даних та бізнес-розробників не хвилює, як це робиться, оскільки це не впливає на них.
Ключова фраза тут, оскільки вона не впливає на них . Реалізація належного розгляду питань має на меті мінімізувати вплив (і, отже, задіяння) інших сторін.
Звичайно, деяких великих змін неможливо уникнути, якщо включити більше однієї особи, наприклад, коли до бази даних буде додано абсолютно нову сутність. Але не варто недооцінювати кількість незначних змін, які ви повинні внести протягом життя програми. Основні зміни - це числова меншість.
What's the benefit of these conversions?
відокремлення моделі даних постійності від запропонованої споживачам моделі даних (представлення). Переваги роз'єднання широко обговорювались у SE, однак мета під DTO полягає в тому, щоб зібрати в одну відповідь стільки інформації, скільки вважається необхідною для клієнтів для збереження дзвінків на сервер. Що робить спілкування клієнт-сервер більш гладким.