NestJs - DTO 和实体
Posted
技术标签:
【中文标题】NestJs - DTO 和实体【英文标题】:NestJs - DTO and Entities 【发布时间】:2020-07-23 04:57:39 【问题描述】:我试图在我的项目中巧妙地使用 DTO 和实体,但它似乎比想象的要复杂。我正在构建一个用于管理库存的后端,我使用 NestJs 和 TypeOrm。
我的客户正在向我发送一组数据并抛出一个 POST 请求,比如说:
"length": 25,
"quantity": 100,
"connector_A":
"id": "9244e41c-9da7-45b4-a1e4-4498bb9de6de"
,
"connector_B":
"id": "48426cf0-de41-499b-9c02-94c224392448"
,
"category":
"id": "f961d67f-aea0-48a3-b298-b2f78be18f1f"
我的控制器负责使用自定义 ValidationPipe 检查字段:
@Post()
@UsePipes(new ValidationPipe())
create(@Body() data: CableDto)
return this.cablesService.create(data);
我在很多地方读到,在最佳实践中,RAW 数据应该转换为 DTO,当涉及到数据插入时,我应该将我的 DTO 转换为 typeOrm 实体。
我对这个方法没问题,但我发现它很复杂,当我的表和前缀名词之间存在关系时更是如此。
这是我的实体电缆
@Entity('t_cable')
export class Cable
@PrimaryGeneratedColumn('uuid')
CAB_Id: string;
@Column(
type: "double"
)
CAB_Length: number;
@Column(
type: "int"
)
CAB_Quantity: number;
@Column()
CON_Id_A: string
@Column()
CON_Id_B: string
@Column()
CAT_Id: string
@ManyToOne(type => Connector, connector => connector.CON_Id_A)
@JoinColumn( name: "CON_Id_A" )
CON_A: Connector;
@ManyToOne(type => Connector, connector => connector.CON_Id_B)
@JoinColumn( name: "CON_Id_B" )
CON_B: Connector;
@ManyToOne(type => Category, category => category.CAB_CAT_Id)
@JoinColumn( name: "CAT_Id" )
CAT: Category;
这是我用于电缆交互的 DTO:
export class CableDto
id: string;
@IsOptional()
@IsPositive()
@Max(1000)
length: number;
quantity: number;
connector_A: ConnectorDto;
connector_B: ConnectorDto;
category: CategoryDto
public static from(dto: Partial<CableDto>)
const it = new CableDto();
it.id = dto.id;
it.length = dto.length;
it.quantity = dto.quantity;
it.connector_A = dto.connector_A
it.connector_B = dto.connector_B
it.category = dto.category
return it;
public static fromEntity(entity: Cable)
return this.from(
id: entity.CAB_Id,
length: entity.CAB_Length,
quantity: entity.CAB_Quantity,
connector_A: ConnectorDto.fromEntity(entity.CON_A),
connector_B: ConnectorDto.fromEntity(entity.CON_B),
category: CategoryDto.fromEntity(entity.CAT)
);
public static toEntity(dto: Partial<CableDto>)
const it = new Cable();
if (dto.hasOwnProperty('length'))
it.CAB_Length = dto.length;
if (dto.hasOwnProperty('quantity'))
it.CAB_Quantity = dto.quantity;
if (dto.hasOwnProperty('connector_A'))
it.CON_Id_A = dto.connector_A.id;
if (dto.hasOwnProperty('connector_B'))
it.CON_Id_B = dto.connector_B.id;
if (dto.hasOwnProperty('category'))
it.CAT_Id = dto.category.id;
return it;
我知道这三种双向转换 DTO 和实体的方法感觉很脏,这就是我在这里的原因..
我的服务对于一个简单的创建或获取请求知道:
async create(dto: CableDto): Promise<CableDto>
const cable = await this.cablesRepository.save(CableDto.toEntity(dto));
return await this.findById(cable.CAB_Id)
我相信有更简单的解决方案可以实现这一目标,或者至少是一种正确的方法。
有什么想法吗?
谢谢。
【问题讨论】:
【参考方案1】:您可以使用 Automapper 来完成 Entity 和 DTO 之间的所有转换。
我在许多项目中使用它并取得了很好的效果,尤其是在具有许多 DTO 和实体的项目中。
例如,您可以这样做:
export class InitializeMapper
mapper: AutoMapper;
constructor()
this.mapper = new AutoMapper();
this.mapper.createMap(CableDto, CableEntitt)
.forMember(dst => dst.length, mapFrom(s => s.length))
.forMember(dst => dst.quantity, mapFrom(s => s.quantity))
.forMember(dst => dst.connector_A, mapFrom(s => s.connector_A.id))
.forMember(dst => dst.connector_B, mapFrom(s => s.connector_B.id))
.forMember(dst => dst.connector_C, mapFrom(s => s.connector_C.id));
);
map(a: any, b: any)
return this.mapper.map(a, b);
mapArray(a: Array<any>, b: any)
return this.mapper.mapArray(a, b);
因此您可以在项目中的任何地方重复使用相同的映射。
问候
【讨论】:
您好 Julio,您的留言。可以向我们展示一个工作示例吗?【参考方案2】:对于所有类型转换(例如 DTO > 实体或实体 > DTO),我开发了一个库 metamorphosis-nestjs 来简化对象的转换。 它为 NestJS 添加了一个缺失的概念,即为您的转换器提供的所有转换提供可注入转换服务,并提前注册到转换服务中(如 Java 应用程序中的 Spring Framework 提供的转换服务)。
所以在你的情况下,使用 typerORM:
npm install --save @fabio.formosa/metamorphosis-nest
import MetamorphosisNestModule from '@fabio.formosa/metamorphosis-nest';
@Module(
imports: [MetamorphosisModule.register()],
...
export class MyApp
import Convert, Converter from '@fabio.formosa/metamorphosis';
@Injectable()
@Convert(CableDto, Cable)
export default class CableDtoToCableConverter implements Converter<CableDto, Promise<Cable>>
constructor(private readonly connection: Connection)
public async convert(source: CableDto): Promise<Cable>
const cableRepository: Repository<Cable> = this.connection.getRepository(Cable);
const target: Product | undefined = await cableRepository.findOne(source.id);
if(!target)
throw new Error(`not found any cable by id $source.id`);
target.CAB_Length = source.length;
target.CAB_Quantity = source.quantity;
... and so on ...
return target;
最后你可以随心所欲地注入和使用conversionService:
const cable = <Cable> await this.convertionService.convert(cableDto, Cable);
从 Cable 到 CableDto 的转换器更简单。
获取README 以了解metamorphosis-nestjs 的示例和所有好处。
【讨论】:
多好的作品啊! Fabio 干得好,这似乎是一个非常有趣的解决方案。即使您的库简化了 DTO 和实体之间需要完成的工作,我的解决方案看起来不错吗? 我同意你的解决方案,但是如果你必须开发“电缆更新”方法,你需要修改toEntity(dto: Partial<CableDto>)
以便通过CableDto.id指定的ID检索电缆。所以你需要将存储库注入到 CableDto 中,但 CableDto 只是一个普通的类。出于这个原因(这不是唯一的原因),我不建议将转换作为静态方法或构造函数。
谢谢!我在后端做了一些事情,我不太喜欢所有这些转换。但我从不向客户端发送实体,我尽可能多地使用我的 DTO 模型,但这并不总是像看起来那么容易。 以上是关于NestJs - DTO 和实体的主要内容,如果未能解决你的问题,请参考以下文章
NestJS & TypeORM:发布请求中的 DTO 格式
NestJS + Typeorm + Graphql:嵌套关系中 DTO 的正确设计模式