Комментарии 17
Почему то всегда думал, что DTO просто для переноса данных, а валидация должна быть в хендлерах
Абсолютно так же мысль возникла и вопрос - неужели так принято в nestjs?
Зависит от ожиданий. Если есть вполне конкретная схема, то выполняя сериализацию в любых других условиях(в т.ч. других языковых платформах) вы уже получаете некоторым образом детерминированное значение. В JS нам должен вернуться объект с приближенным форматом данных(там где было число в JSON, там оно и должно появиться в объекте). В соответствии с этим правилом, фреймворк нам предлагает сериализовать его еще более строго, так почему же не воспользоваться этим? Всё это работает как бы за кулисами, где-то в декораторах/интерсепторах, при этом сама эта логика сериализации и валидации для самого, условного, контроллера совсем не важна и не нужна 🤷🏼♂️
extends WithName(WithEmail(WithPassword(class {})))
А почему бы не использовать IntersectionType()?
https://docs.nestjs.com/openapi/mapped-types
/IntersectionType
Он в том числе с метаданными сваггер-декораторов полей нормально отрабатывает, перетаскивает их в результирующий класс.
Как я уже ответил, да, можно использовать и IntersectionType. Я больше в целом о подходе собирать DTO из набора полей, на сколько это удобно или нет, в тех или иных случаях, и какие для этого есть средства в том же NestJS. IntersectionType сам не использовал.
Ваш велосипед очень громоздкий, откройте документацию там есть примеры, DTO - суть в его названии.
Понятно что дублирование полей не айс, но всё уже придуманно до нас:
export class DeleteLicDto extends OmitType(LicDto, ['code', 'expireDate']) {
@Expose()
@ApiProperty({ default: 1, required: false })
@IsNumber()
@IsOptional()
@NotEquals(0)
@Type(() => Number)
code?: number;
}
Можно и с дженериками, конечно, нечто подобное хотел увидеть в статье..
export abstract class ConstructableDto<
T = ConstructableDto<Record<string, unknown>>,
> {
constructor(dto?: Partial<T>) {
if (dto) {
Object.assign(this, dto);
}
}
}
export class SuccessDto extends ConstructableDto<SuccessDto> {
@Expose()
@ApiProperty({
type: Boolean,
description: 'Is success execute request',
example: true,
})
success: boolean;
}
export class ListResponseDto<
T = Record<string, unknown>,
> extends ConstructableDto<ListResponseDto<T>> {
@ApiProperty({ description: 'Selected data' })
data!: T[];
@ApiProperty({ description: 'Total numbers of entries' })
count!: number;
}
Но в вашем случае это:
https://docs.nestjs.com/techniques/validation
Ну не знаааю. Это какой-то нечитаемый DRY obsession.
export class CreateUserDto extends WithName(WithEmail(WithPassword(class {}))) {}
WithName
означает присутствиеname
или, например,firstName
+lastName
(для краткости)?Когда полей будет с десяток, получится LISPообразная вязанка. Разобрать её визуально будет сложно.
У класса из N полей N+2 закрывающих скобок почти подряд. Обычно - одна.
DRY номинально есть, но по факту копипаст тот ещё.
Нормальная идиоматичная для TS композиция покинула чат.
Ctrl+Click будет перекидывать в какое-то изолированное объявление поля (файл with-x). Find all references тоже вряд ли выдаст все неабстрактные классы с этим полем.
Может, просто декораторы композировать? Ну там IsPassword
сделать какой-нибудь, если Nest это поймёт.
Претензии, конечно, на уровне не знаю даже кого, нытика, наверное?
Вот та же претензия про Name - кто тебе мешает нормальный нейминг делать? Или WithFirstName, например, или добавлять jsdoc комменты? Никто
Кто тебе мешает кастомный рул для линтера написать, чтобы форматировать вязанку, как тебе нравится? Никто
Ну и короче так по каждому пункту можно пройтись.
По факту подход хороший, его просто надо адаптировать под себя, что тоже нормально.
По поводу композиции тоже хорошая идея. Но тут всегда есть риски, что придется декомпоизировать какой-то декоратор на 2 разных. Учитывая определенные риски, то имеет место быть
О да, я бы не просто заныл... Я бы даже завыл увидев как "with? name" повторяется семь раз по двум файлам ради одного поля.
// dto/fields/with-name.dto.ts
import { IsString, Length } from 'class-validator';
export function WithName<T extends new (...args: any[]) => any>(Base: T) {
abstract class WithName extends Base {
@IsString()
@Length(2, 50)
name: string;
}
return WithName;
}
// dto/create-user.dto.ts
import { WithName } from './fields/with-name.dto';
export class CreateUserDto extends WithName(class {}) {}
вместо
import { IsString, Length } from 'class-validator';
export class CreateUserDto {
@IsString()
@Length(2, 50)
name: string;
}
Кастомные рулы линтера и прочий суперклей применимы к традиционному подходу тоже.
Нет опыта в бекенде через Nest Js, Но выглядит красиво. Есть кто пишет на Next js?
Omg, что мешает просто использовать
import { OmitType, PartialType } from '@nestjs/mapped-types'
И тогда не будет речи о том чтобы дублировать поля в разных файлах/классах, а так же есть возможность их переопределить
Есть такое, self-describe code. Это когда код говорит "я Дартаньян, а ты г..но", как этот)))
Логичнее делать атрибуты/аннотацию/декораторы, которые комбинируют ограничения, параметры определенных логических типов данных. Например, у вас есть поле телефон, заводите аннотацию Телефон, которая уже в себе содержит и регесп и максимальную длину и прочее. Не знаю насколько это возможно в конкретном фреймворке.
Я оставляю этот вопрос интерфейсам.
От них уже создаю dto, используя интерфейсы как в чистом виде, так и пики, partal...
Так красивее получается.
Композиция DTO в TypeScript