如何减少TypeScript搭建Node gRPC服务时请求响应处理的冗余代码
可行的优化方案
1. 实现通用消息实例转换工具
你可以基于所有生成的jspb.Message子类的公共特征,写一个泛型转换函数,一次性解决手动调用setter的冗余问题:
type Constructor<T> = new () => T; type MessageAsObject<T extends jspb.Message> = T extends { toObject(): infer O } ? O : never; function createMessageInstance<T extends jspb.Message>( MessageCls: Constructor<T>, data: MessageAsObject<T> ): T { const instance = new MessageCls(); Object.entries(data).forEach(([key, value]) => { const setterName = `set${key.charAt(0).toUpperCase() + key.slice(1)}`; // 生成的消息类的setter都符合该命名规则,可放心调用 (instance as any)[setterName](value); }); return instance; }
使用后原来的映射逻辑可以直接简化为:
const dogEntities = dogs.map(dogObj => createMessageInstance(DogEntity, dogObj));
2. 封装通用RPC处理器模板
针对CRUD类的固定逻辑(单查询、列表查询、新增、更新、删除),你可以提前封装对应高阶函数,直接复用重复的请求响应处理逻辑,比如针对服务端流的列表查询封装:
import * as grpc from '@grpc/grpc-js'; import { Empty } from './generated/proto/common_pb'; type ListHandlerOptions<Res extends jspb.Message, Entity> = { ResponseClass: Constructor<Res>, fetchData: () => Promise<Entity[]> } function createServerStreamListHandler<Res extends jspb.Message, Entity>( opts: ListHandlerOptions<Res, Entity> ) { return async (call: grpc.ServerWritableStream<Empty, Res>) => { const dataList = await opts.fetchData(); dataList.forEach(item => { const resInstance = createMessageInstance(opts.ResponseClass, item as any); call.write(resInstance); }); call.end(); } }
封装完成后,你的getAllDogs方法可以直接简化为一行声明:
getAllDogs = createServerStreamListHandler({ ResponseClass: DogEntity, fetchData: () => dogService.repo.findAll() })
3. 更换代码生成实现
如果不想依赖grpc_tools_node_protoc生成的基于setter的消息类,可以更换为支持直接使用POJO对象的protobuf TypeScript生成方案,生成的消息直接为普通TS类型,不需要实例化和调用setter,从根源消除转换冗余,处理器里可以直接将业务对象作为响应返回。
内容的提问来源于stack exchange,提问作者bschulte3
相关产品推荐
相关产品推荐

