JavaRush /Курси /C# SELF /Проблема з класами для передачі даних (DTO)

Проблема з класами для передачі даних (DTO)

C# SELF
Рівень 19 , Лекція 0
Відкрита

1. Знайомство з DTO

DTO (Data Transfer Object, «об’єкт передавання даних») — термін із професійного програмування, особливо зі світу розподілених застосунків і сервісів (наприклад, коли один сервіс викликає інший через мережу). По суті, це просто об’єкт — здебільшого звичайний клас C#, — чиє єдине завдання: містити набір даних і безпечно передаватися між шарами програми або між застосунками.

Уявімо чат між клієнтом і сервером. Щоразу, коли користувач надсилає повідомлення, клієнт формує об’єкт із текстом, часом і ім’ям користувача, надсилає його серверу, а той уже вирішує, що з цим робити. Добре, коли в цьому об’єкті немає нічого зайвого: лише необхідне для передавання даних. Це і є клас DTO.

public class UserDto
{
    public int Id { get; set; }
    public string Name { get; set; }
}
Класичний DTO: лише властивості, без логіки

Усе! Ніякої бізнес-логіки, жодних складних методів — лише властивості.

Класи лише для передавання даних

Уявіть, що ви зайнялися доставкою піци. Ваш кур’єр (DTO) не повинен уміти готувати піцу, приймати замовлення чи лагодити велосипед. Усе, що від нього потрібно, — донести коробку з точки А в точку Б. Так і клас DTO: його завдання — зберігати прості дані. Він не повинен сам себе валідувати, не повинен знати, як правильно відображатися в UI — лише зберігати.

  • DTO — це «люди без професії» або «об’єкти без логіки», просто переносять дані.
  • Їх використовують, щоб не перевантажувати бізнес-логіку деталями передавання даних: що менша валіза, то легше її нести.

2. Проблеми звичайних класів DTO на практиці

Здавалося б, ось воно щастя: оголосили клас із автовластивостями — і все готово. Але не все так просто.

Змінність даних: що може піти не так

public class ProductDto
{
    public int Id { get; set; }
    public string Name { get; set; }
}
DTO з публічними set — легко випадково змінити дані

А тепер уявіть, що десь у коді випадково змінили властивість:

ProductDto dto = new ProductDto { Id = 1, Name = "Молоко" };
SomeApiProcess(dto);
dto.Name = "Кефір"; // Ой!

Через те, що всі властивості мають публічний set, легко випадково зіпсувати об’єкт або, ще гірше, змінити його в одному місці так, що наслідки непомітно розійдуться системою. Це стає реальною проблемою, коли об’єкти використовують багато модулів і потоків.

Копіювання й порівняння — нескінченна рутина

Припустімо, ви хочете створити копію ProductDto, але змінити тільки ім’я.

var otherDto = new ProductDto { Id = dto.Id, Name = "Творог" };

Якщо властивостей багато, це перетворюється на нудний і потенційно помилковий процес ручного копіювання кожного поля.

А якщо вам ще й потрібно порівнювати об’єкти? Доведеться перевизначити Equals і GetHashCode — це часто роблять абияк або й узагалі забувають.

Незмінність — не для „простих“ класів

У більшості випадків DTO мають бути незмінними: один раз отримали дані — і більше їх не змінюємо. Але клас із автовластивостями не підходить для цього: будь-хто може змінити будь-які поля.

3. Як це виглядає на практиці у великому проєкті

Розгляньмо приклад із життя.

Ви — розробник у величезному інтернет‑магазині. Вам потрібно передати інформацію про замовлення мережею. Для цього ви створюєте такий клас:

public class OrderDto
{
    public int Id { get; set; }
    public string Customer { get; set; }
    public double Total { get; set; }
}

Усе працює, доки не з’являються сотні тисяч замовлень на тиждень. Безліч мікросервісів починають передавати DTO‑об’єкти туди‑сюди; хтось десь забуває, що Total слід обчислювати, а не задавати вручну; хтось випадково змінює Customer після надсилання… І ось у вас з’являються помилки, які дуже складно відстежити.

Як можна покращити?

  • Використовувати лише get-властивості (без set).
  • Передбачити спеціальний конструктор, щоб установлювати значення лише під час створення.
  • Перевизначити Equals і GetHashCode

Розумієте, до чого це веде? Те, що мало бути простою «валізою», раптом перетворюється на щось громіздке й незручне.

4. Короткий розбір типових проблем DTO‑класів

У невеликих проєктах помилки, спричинені зміною DTO‑об’єктів, ще можна «впіймати вручну». У великих — це катастрофа. DTO використовують для авторизації, передавання коштів, обробки замовлень — будь-яка несанкціонована зміна даних може обернутися грошовими втратами або навіть кримінальною справою.

Нижче наведено «шпаргалку» проблем, із якими стикається майже кожен, хто пише DTO на класах:

Проблема Опис
Змінність Дані легко змінювати де завгодно — з’являється ризик помилок (особливо в багатопоточності).
Клонування Копіювати об’єкти незручно; часто роблять це вручну.
Порівняння За замовчуванням порівнюються за посиланням, а не за значенням.
Підтримка with-копій Хотілося б копіювати зі зміною частини даних без ручної роботи.
Неочевидна вкладеність Вкладені DTO доводиться копіювати повністю, а це обтяжливо.

5. Приклад: передавання даних між шарами у нашому застосунку

Припустімо, у нас є застосунок‑щоденник, де ми можемо зберігати завдання. Раніше ми б мали такий клас:

public class TaskDto
{
    public int Id { get; set; }
    public string Description { get; set; }
    public DateTime DueDate { get; set; }
}

Ми читали TaskDto з файлу, потім додавали його в колекцію, потім відображали на екрані. Усе було добре… допоки хтось з іншого модуля випадково не змінив DueDate просто перед серіалізацією — і в користувача виникла плутанина.

7. Чи можна краще?

Так! І не лише можна, а й варто. Для розв’язання цих проблем у C# з’явилася спеціальна конструкція — record. Вона знімає більшість проблем, пов’язаних із класами DTO, майже магічним чином.

Що робить record:

  • За замовчуванням орієнтований на значення: порівняння об’єктів за вмістом, а не за посиланням.
  • Незмінність: можна оголосити лише get-властивості, значення встановлюються через конструктор.
  • Легке клонування: є конструкція with для копіювання зі зміною.
  • Автоматична генерація методів для порівняння й виведення.
public record TaskDto(int Id, string Description, DateTime DueDate);
DTO на основі record: компактно, безпечно, зручно!

Чудово? Авжеж! Та докладно про це — у наступній лекції.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ