Ответ
Да, если DTO (Data Transfer Object) имеют полностью идентичную структуру и семантику, я выношу их в общий базовый класс или интерфейс. Это следует принципу DRY (Don't Repeat Yourself) и упрощает поддержку.
Пример на TypeScript для Node.js/Express:
// 1. Общий базовый интерфейс
interface BaseUserDto {
email: string;
firstName: string;
lastName: string;
}
// 2. Специфичные DTO наследуют или расширяют базовый
interface UserCreateDto extends BaseUserDto {
password: string; // Дополнительное поле только для создания
}
interface UserUpdateDto extends BaseUserDto {
// Пока полей нет, но структура общая
}
interface UserResponseDto extends BaseUserDto {
id: string;
createdAt: Date;
}
// 3. Использование в контроллере Express
app.post('/users', (req: Request<{}, {}, UserCreateDto>, res: Response) => {
// Валидация и обработка req.body
});
app.patch('/users/:id', (req: Request<{ id: string }, {}, UserUpdateDto>, res: Response) => {
// Обновление пользователя
});
Когда НЕ стоит объединять:
- Если DTO могут эволюционировать по-разному (например, в
UserUpdateDtoполеemailстанет опциональным, а вUserCreateDto— обязательным). - Если они используются в разных bounded context'ах (доменная модель разная). В таких случаях дублирование может быть менее вредным, чем неправильная абстракция.
Ответ 18+ 🔞
Да ты посмотри, какие у нас тут DTOшки развелись, как тараканы после ремонта! Ситуация, в общем-то, классическая: пишешь один, пишешь второй, а на третий уже подозрение ёбать чувствую — да это же одно и то же, ёпта!
Если у них структура и смысл полностью идентичные, то держать кучу копий — это ж чистой воды распиздяйство. Тут даже принцип есть такой, DRY называется — Don't Repeat Yourself, или, как я это понимаю, «не выёбывайся, не повторяй одно и то же».
Вот смотри, как это по-человечески делается на TypeScript'е. Просто, без наворотов.
// 1. Делаем общую основу, как фундамент для избушки
interface BaseUserDto {
email: string;
firstName: string;
lastName: string;
}
// 2. А теперь все остальные DTO от этой основы пляшут
interface UserCreateDto extends BaseUserDto {
password: string; // Вот, например, при создании пароль нужен, а так всё то же самое
}
interface UserUpdateDto extends BaseUserDto {
// Пока тут пусто, но структура-то общая! И если что-то добавим — будет видно.
}
interface UserResponseDto extends BaseUserDto {
id: string;
createdAt: Date; // И клиенту отдаём немного больше инфы
}
// 3. Используем там, где надо
app.post('/users', (req: Request<{}, {}, UserCreateDto>, res: Response) => {
// Работаем с req.body, там всё по контракту
});
app.patch('/users/:id', (req: Request<{ id: string }, {}, UserUpdateDto>, res: Response) => {
// Апдейтим юзера
});
Всё, красота. Одна правка в BaseUserDto — и во всех наследниках автоматом. Удивление пиздец, как удобно!
Но! Тут есть один важный момент, блядь. Не надо сходить с ума и всё подряд в кучу сгребать. Не стоит объединять, если:
- Они могут меняться по-разному. Вот представь: завтра в
UserUpdateDtoполеemailстанет необязательным (типа, хочешь — поменяй почту, не хочешь — и так сойдёт), а вUserCreateDtoоно обязательным и останется. Если они от одного предка — начинается пиздец, придётся костыли пилить. Лучше уж пусть будут отдельными, но честными. - Они из разных «вселенных» (bounded context). Если один DTO для админки, а другой для публичного API, и даже поля с одним названием
emailнесут разный смысл — это манда с ушами получится, если их объединить. Тут дублирование — меньшее из зол, честно.
Короче, чувак, правило простое: если реально одно и то же — выноси. Если есть хоть тень сомнения, что они разойдутся — оставь как есть, терпения ноль ебать на будущие костыли. Голова должна думать, а не только клавиатуру обивать.