Dependency injection 如何使用类型GraphQL、类型ORM和依赖项注入实现解析器继承

Dependency injection 如何使用类型GraphQL、类型ORM和依赖项注入实现解析器继承,dependency-injection,factory,typeorm,typegraphql,Dependency Injection,Factory,Typeorm,Typegraphql,我试图扩展GraphQL类型提供的解析器继承示例,只是用类型化存储库替换静态数据 以下是PersonResolver如何扩展ResourceResolver,以及它如何将persons数组作为ResourceResolver构造函数的第二个参数传递 const persons: Person[] = [ { id: 1, name: "Person 1", age: 23, role: PersonRole.Normal, }, {

我试图扩展GraphQL类型提供的解析器继承示例,只是用类型化存储库替换静态数据

以下是PersonResolver如何扩展ResourceResolver,以及它如何将
persons
数组作为ResourceResolver构造函数的第二个参数传递

const persons: Person[] = [
  {
    id: 1,
    name: "Person 1",
    age: 23,
    role: PersonRole.Normal,
  },
  {
    id: 2,
    name: "Person 2",
    age: 48,
    role: PersonRole.Admin,
  },
];

@Resolver()
export class PersonResolver extends ResourceResolver(Person, persons) {
   ...
}
在ResourceResolver内部

export function ResourceResolver<TResource extends Resource>(
  ResourceCls: ClassType<TResource>,
  resources: TResource[],
) {
  const resourceName = ResourceCls.name.toLocaleLowerCase();

  // `isAbstract` decorator option is mandatory to prevent multiple registering in schema
  @Resolver(_of => ResourceCls, { isAbstract: true })
  @Service()
  abstract class ResourceResolverClass {
    protected resourceService: ResourceService<TResource>;

    constructor(factory: ResourceServiceFactory) {
      this.resourceService = factory.create(resources);
    }
...
}
导出函数资源解析程序(
ResourceCls:类类型,
资源:TResource[],
) {
const resourceName=ResourceCls.name.toLocaleLowerCase();
//`isAbstract`decorator选项是必需的,以防止在架构中多次注册
@解析器(_of=>ResourceCls,{isastract:true})
@服务()
抽象类ResourceResolverClass{
受保护的资源服务:资源服务;
构造函数(工厂:ResourceServiceFactory){
this.resourceService=factory.create(资源);
}
...
}
在资源服务工厂

@Service()
export class ResourceServiceFactory {
  create<TResource extends Resource>(resources?: TResource[]) {
    return new ResourceService(resources);
  }
}

export class ResourceService<TResource extends Resource> {
  constructor(protected resources: TResource[] = []) {}

    getOne(id: number): TResource | undefined {
    return this.resources.find(res => res.id === id);
}

@Service()
导出类ResourceServiceFactory{
创建(资源?:TResource[]){
返回新的ResourceService(资源);
}
}
导出类资源服务{
构造函数(受保护的资源:TResource[]=[]){}
getOne(id:number):TResource |未定义{
返回this.resources.find(res=>res.id==id);
}
我想知道实现ResourceResolver的最佳方法,但我不想传递静态数据,而是想从TypeORM传递一个存储库

以下是原始示例-


非常感谢您提供的任何帮助或建议。

我相信您必须在解析器函数中执行类似的操作:

function createBaseResolver<T extends BaseEntity>(suffix: string, objectTypeCls: T) {
    @Resolver({ isAbstract: true })
    abstract class BaseResolver {  
      @Query(type => [objectTypeCls], { name: `getAll${suffix}` })
      async getA(@Arg("id", type => Int) id: number): Promise<T> {
          let beCastedObj = (<typeof BaseEntity> objectTypeCls.constructor); // https://github.com/Microsoft/TypeScript/issues/5677
          return beCastedObj.findOne({ where: { id:id } }) as Promise<T>;
      }
    }
  
    return BaseResolver;
  }
编辑:OP在评论中问他是否认为这是一个好的模式。我才刚刚开始了解这一点,所以不要把我的话当作福音。但是,在下一步:定义自定义参数时,我遇到了麻烦。如果您对@Args()使用自定义类(推荐使用,因为它可以自动验证),则typescript/typegraphql在编译时需要该信息。如果这些参数不会在任何子级之间更改,则可以保持父级的解析程序不变,但如果这些参数更改,则需要一种传入自定义参数的方法

我通过将分解器装饰器移到孩子们身上实现了这一点

示例家长:

abstract class BaseUserCreatedEntityResolver {

    async get(args: any, ctx: any): Promise<T> {
      this.checkForLogin(ctx);
      let beCastedObj = (<typeof UserCreatedEntity>objectTypeCls.constructor);
      args = Object.assign(args, { userCreator: ctx.req.session.userId })
      let a =  beCastedObj.findOne({ where: args }) as any;
      return a;
    }

    async getAll(args: any, ctx: any): Promise<T> {
      this.checkForLogin(ctx);
      let beCastedObj = (<typeof UserCreatedEntity>objectTypeCls.constructor);
      args = Object.assign(args, { userCreator: ctx.req.session.userId });
      beCastedObj.create(args);
      return beCastedObj.find({ where: args }) as any;
    }

    async add(args:any, ctx: any): Promise<T> {
      this.checkForLogin(ctx);
      let beCastedObj = (<typeof UserCreatedEntity>objectTypeCls.constructor);
      args = Object.assign(args, { userCreator: ctx.req.session.userId });
      let entity = await beCastedObj.create(args)[0];
      await entity.save();
      return entity as any;
    }

    async delete(args:any, ctx: any): Promise<T> {
      this.checkForLogin(ctx);
      let entity = await this.get(args,ctx);
      await entity.remove();
      return new Promise(()=>true);
    }

    async update(args:any, ctx: any): Promise<T> {
      this.checkForLogin(ctx);
      let entity = await this.get(args,ctx);
      delete args['userCreator'];// this should've been filtered out in child param definition, but adding it here just in case

      Object.assign(entity,args);
      await entity.save();
      return entity;
    }

    checkForLogin(ctx:any){
      if(!ctx.req.session.userId) throw new Error("User not logged in");
    }

  }

我非常喜欢这种模式。如果我有一个函数在所有子解析程序中都不会改变,我将创建该函数并在父函数中进行修饰。因此,我将把父函数作为一个可自定义的函数类,而不仅仅是一个基本类。

感谢您抽出时间回答我的问题。如果您是e经验丰富的TypeGraphQL和TypeForm你认为这种模式值得实施吗?再次感谢你的帮助。我很高兴:)。答案对你的问题来说太长了,所以我在上面编辑了我的答案。哇,这是一个非常酷的范例!我理解这个概念(感谢你的解释)但是我仍然怀疑我自己的实现。在你看来,你认为我应该遵循这个范例还是简单地在我的解析器中重复我自己——考虑到我的应用程序中只有不到10个实体。我基本上只是使用一个父类。它有助于从子类中删除大量代码(保持干燥)。如果任何子级需要自定义解析器,请不要使用
super
调用。你可以这样做!删除令人困惑的TypeGraphQL父级解析器内容,使其成为一个普通的JS类。然后复制并粘贴我的代码(如果不需要,请删除会话/登录内容)然后把它放在父类中。我继续复制了我所有的父函数,希望这会有所帮助。另一件事你可以做的就是在没有父类的情况下开始编写子类,然后当你对复制+粘贴切换到使用父类感到沮丧时。如果你从未感到沮丧,那么你可能不需要切换“过早优化”[代码]
abstract class BaseUserCreatedEntityResolver {

    async get(args: any, ctx: any): Promise<T> {
      this.checkForLogin(ctx);
      let beCastedObj = (<typeof UserCreatedEntity>objectTypeCls.constructor);
      args = Object.assign(args, { userCreator: ctx.req.session.userId })
      let a =  beCastedObj.findOne({ where: args }) as any;
      return a;
    }

    async getAll(args: any, ctx: any): Promise<T> {
      this.checkForLogin(ctx);
      let beCastedObj = (<typeof UserCreatedEntity>objectTypeCls.constructor);
      args = Object.assign(args, { userCreator: ctx.req.session.userId });
      beCastedObj.create(args);
      return beCastedObj.find({ where: args }) as any;
    }

    async add(args:any, ctx: any): Promise<T> {
      this.checkForLogin(ctx);
      let beCastedObj = (<typeof UserCreatedEntity>objectTypeCls.constructor);
      args = Object.assign(args, { userCreator: ctx.req.session.userId });
      let entity = await beCastedObj.create(args)[0];
      await entity.save();
      return entity as any;
    }

    async delete(args:any, ctx: any): Promise<T> {
      this.checkForLogin(ctx);
      let entity = await this.get(args,ctx);
      await entity.remove();
      return new Promise(()=>true);
    }

    async update(args:any, ctx: any): Promise<T> {
      this.checkForLogin(ctx);
      let entity = await this.get(args,ctx);
      delete args['userCreator'];// this should've been filtered out in child param definition, but adding it here just in case

      Object.assign(entity,args);
      await entity.save();
      return entity;
    }

    checkForLogin(ctx:any){
      if(!ctx.req.session.userId) throw new Error("User not logged in");
    }

  }
@ArgsType()
class GetAllArgs {
  @Field()
  date:Date;
}

//...

@Query(() => Entity)
async getAllEntitiesName(@Args() args :GetAllArgs, @Ctx() ctx: any) {
  return super.get(args,ctx);
}