我正在尝试在我的项目中巧妙地使用DTO和Entities,但它似乎比它本来要复杂。我正在建立一个用于管理库存的后端,我使用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)
}
我相信,有更简单的解决方案可以实现这一目标,或者至少是一种正确的实现方式。
有什么主意吗?
谢谢。
答案 0 :(得分:0)
对于所有类型转换(例如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的所有好处。
答案 1 :(得分:0)
您可以使用 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);
}
}
因此您可以在项目中的任何位置重复使用相同的映射。
问候