【问题标题】:How to implement Resolver-Inheritance with Type-GraphQL, TypeORM, and dependency injection如何使用 Type-GraphQL、TypeORM 和依赖注入实现 Resolver-Inheritance
【发布时间】:2021-01-03 20:06:29
【问题描述】:

我正在尝试扩展 Type-GraphQL 提供的解析器继承示例,但将静态数据替换为 TypeORM 存储库。

下面是 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);
    }
...
}

在 ResourceServiceFactory 中

@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);
}

我想知道实现 ResourceResolver 的最佳方式,但我想从 TypeORM 传递一个存储库,而不是静态数据。

这是原始示例 - https://github.com/MichalLytek/type-graphql/tree/master/examples/resolvers-inheritance

非常感谢任何帮助或建议。

【问题讨论】:

    标签: dependency-injection factory typeorm typegraphql


    【解决方案1】:

    我相信你必须在你的解析器函数中做一些像这样时髦的事情:

    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;
      }
    

    在另一个文件中...

    const PersonBaseResolver = createBaseResolver("person", Person);
    
    @Resolver(of => Person)
    export class PersonResolver extends PersonBaseResolver {
      // ...
    }
    

    编辑:OP 询问他是否认为这是 cmets 中的一个好模式。我刚开始学习这个,所以不要把我的话当作福音。然而。我在进行下一步时遇到了麻烦:定义自定义参数。如果您为 @Args() 使用自定义类(推荐因为它可以自动验证),那么 typescript/typegraphql 在编译时需要该信息。如果这些 args 不会在任何子级之间发生变化,您可以保持该父级的解析器不变,但如果它们发生变化,您需要一种方法来传递自定义参数。

    我通过将解析器装饰器移动到子级来实现这一点。

    示例父级:

    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);
    }
    

    我非常喜欢这种范式。如果我有一个在所有子解析器中都不会改变的函数,我将创建该函数并在父解析器中装饰它。出于这个原因,我将父类保留为可自定义的函数类,而不仅仅是一个基本类。

    【讨论】:

    • 感谢您抽出宝贵时间回答我的问题。如果您对 TypeGraphQL 和 TypeORM 有经验,您认为这种模式值得实施吗?再次感谢您的帮助。
    • 我的荣幸:)。您的问题的答案太长了,所以我在上面编辑了我的答案。
    • 哇,这是一个非常酷的范例!我理解这个概念(感谢您的解释),但我仍然怀疑自己的实施。在您看来,您认为我应该追求这种范式还是简单地在我的解析器中重复自己 - 考虑到我的应用程序中的实体少于 10 个。
    • 我基本上只是在使用父类。它有助于减少孩子们的大量代码(保持干燥)。如果任何孩子需要自定义解析器,请不要使用super 调用。你能行的!删除令人困惑的 TypeGraphQL 父解析器内容,并使其成为普通的 JS 类。然后复制+粘贴我的代码(如果您不需要,请删除会话/登录内容)并将其放在父级中。我继续复制了我所有的父函数,希望能有所帮助。
    • 您可以做的另一件事是开始编写没有父类的子类,然后当您对复制+粘贴感到沮丧时切换到使用父类。如果您永远不会感到沮丧,那么您可能不需要切换!减少“过早优化”[代码]
    猜你喜欢
    • 1970-01-01
    • 2019-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    • 2021-01-18
    相关资源
    最近更新 更多