完全指南:基于抽象类定义 GraphQL Interface 及继承实现)
后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载TypeGraphQL 的核心思想是基于 TypeScript 类创建 GraphQL 类型。本指南以 version-2.0.0-rc.3 的 interfaces 文档 为主线系统讲解如何用InterfaceType()装饰器把抽象类映射为 GraphQL Interface 类型如何让 ObjectType 实现接口、接口继承接口、为接口字段编写解析器以及resolveType与自动注册的底层原理。读完本文你将能独立设计出可复用、支持多态查询的接口化 schema并理解其背后的 schema 生成机制。为什么需要 GraphQL Interface在面向对象编程中接口interface用于描述实现它的类必须遵守的契约。GraphQL 同样提供了Interface Type它定义了一组必须共同拥有的字段任何实现该接口的具体对象类型Object Type都必须包含这些字段从而让查询方可以安全地通过接口访问公共字段。TypeGraphQL 允许你在 TypeScript 侧定义一个接口形状然后在运行时由 schema 生成器把它翻译成 GraphQL SDL 中的interface定义。这样你只需要维护一份 TypeScript 契约GraphQL 层的接口会自动跟随无需手动同步两份定义。用抽象类代替 TypeScript interfaceTypeScript 原生interface只存在于编译期运行时会完全消失因此装饰器decorator无法在运行时读取它们来构建 schema。TypeGraphQL 给出的解决方案是使用抽象类abstract class扮演接口的角色。抽象类与接口几乎等价——它不能被实例化但可以被其他类继承实现唯一的差别是它不会强制开发者实现某个方法或初始化某个字段。因此只要我们在使用抽象类时自律地把它当作接口来对待就可以安全地使用它InterfaceType() abstract class IPerson { Field(type ID) id: string; Field() name: string; Field(type Int) age: number; }从源码上看InterfaceType装饰器最终会把类信息收集进全局元数据存储getMetadataStorage().collectInterfaceMetadata(...)供 schema-generator.ts 在构建 schema 时消费对应的元数据结构定义在 interface-class-metadata.ts。让 ObjectType 实现接口定义好接口类型类之后就可以像使用 TypeScript 接口一样在对象类型类中实现它ObjectType({ implements: IPerson }) class Person implements IPerson { id: string; name: string; age: number; }唯一需要额外做的是通过implements参数告诉 TypeGraphQL 这个ObjectType实现了哪个InterfaceType。实现多个接口时传入数组ObjectType({ implements: [IPerson, IAnimal, IMachine] }) class Hybrid implements IPerson, IAnimal, IMachine { // ... }两种实现方式方式一省略字段装饰器隐式实现接口的字段会从接口定义中直接复制到对象类型因此你可以完全省略对象类型上的字段装饰器只依赖 TypeScript 类型检查来保证实现正确。这样你只需维护接口这一份定义避免重复ObjectType({ implements: IPerson }) class Person implements IPerson { id: string; name: string; age: number; // 无需再写 Field }从 schema-generator.ts 的实现可以看到schema 生成器会先取接口定义的所有字段fieldsMetadata.push(...(it.fields || []))再把对象类型自身的字段追加在后面实现继承 覆盖的效果。方式二继承抽象类extends也可以直接继承基类抽象类接口的所有字段都会随继承链被发射到 schema 中只需在子类中补充自己的新字段ObjectType({ implements: IPerson }) class Person extends IPerson { Field() hasKids: boolean; }同理schema-generator.ts在处理扩展类时会通过原型链查找父类的字段信息getSuperClassType()/getFieldMetadataFromObjectType(...)将父类字段合并进当前类型。接口实现其他接口自graphql-js15.0 起GraphQL 接口类型也可以实现其他接口类型。TypeGraphQL 使用与对象类型完全相同的语法——implements装饰器选项InterfaceType() class Node { Field(type ID) id: string; } InterfaceType({ implements: Node }) class Person extends Node { Field() name: string; Field(type Int) age: number; }当接口已经实现了其他接口时无需在ObjectType的implements数组中罗列全部接口只写继承链上最近的一个即可。例如Student实现了Person而Person实现了Node那么ObjectType({ implements: [Person] }) class Student extends Person { Field() universityName: string; }该示例最终生成的 GraphQL SDL 如下interface Node { id: ID! } interface Person implements Node { id: ID! name: String! age: Int! } type Student implements Node Person { id: ID! name: String! age: Int! universityName: String! }注意 SDL 中Student的implements列表里同时出现了Node Person——这正是 schema 生成器复制父类接口逻辑的体现在 schema-generator.ts 与 schema-generator.ts 中生成器会把父类已实现的接口合并进当前类型的interfaces集合interfaces Array.from(new Set(interfaces.concat(superInterfaces)))。接口字段的解析器与参数在接口类内部直接实现解析方法接口字段的解析器可以直接定义在接口类的方法里语法与对象类型完全一致并且会被所有未提供自身实现的实现类自动继承InterfaceType() abstract class IPerson { Field() firstName: string; Field() lastName: string; Field() fullName(): string { return ${this.firstName} ${this.lastName}; } }接口字段携带参数如果接口字段需要接收参数例如 SDL 中的avatar(size: Int!): String!照常使用Arg或Args装饰器即可InterfaceType() abstract class IPerson { Field() avatar(Arg(size) size: number): string { return http://i.pravatar.cc/${size}; } }仅约束签名而不提供实现TypeScript 不允许在抽象方法上使用装饰器因此如果你想只约束签名参数与返回类型不提供实现需要在方法体内主动抛出错误InterfaceType() abstract class IPerson { Field() avatar(Arg(size) size: number): string { throw new Error(Method not implemented!); } }然后所有实现该接口的对象类型都必须继承并覆写该方法提供真实实现ObjectType({ implements: IPerson }) class Person extends IPerson { avatar(size: number): string { return http://i.pravatar.cc/${size}; } }这一点与仓库示例 examples/interfaces-inheritance/person/person.interface.ts 中的写法完全一致——示例中的IPerson.avatar方法同样抛出Method not implemented.由各具体类型提供实现。扩展签名补充额外参数若实现类想扩展签名例如在size之外增加format参数必须重新声明整个字段签名而不能只写增量部分ObjectType({ implements: IPerson }) class Person implements IPerson { Field() avatar(Arg(size) size: number, Arg(format) format: string): string { return http://i.pravatar.cc/${size}.${format}; } }在解析器类中用 FieldResolver 定义接口字段解析器接口类型字段的解析器也可以定义在解析器类层面通过FieldResolver装饰器实现。这种方式适合需要访问上下文context/info或依赖注入场景Resolver(of IPerson) class IPersonResolver { FieldResolver() avatar(Root() person: IPerson, Arg(size) size: number): string { return http://typegraphql.com/${person.id}/${size}; } }schema-generator.ts会为接口的每个字段在元数据存储中查找对应的fieldResolvers元数据kind internal或解析器类被显式包含时命中则使用createAdvancedFieldResolver创建解析函数否则回退到createBasicFieldResolver参见 schema-generator.ts。接口类型在 schema 中的注册与 orphanedTypes默认行为自动注册所有实现类默认情况下只要接口类型被 schema 显式使用例如作为某个 Query/Mutation 的返回类型或某个字段的类型所有实现该接口的对象类型都会被自动发射到 schema 中无需额外操作。关闭自动注册autoRegisterImplementations但在某些场景下例如基于 Relay 的系统中常见的Node接口需要同时暴露多个相互隔离的 schema如公开 schema 与私有 schema自动注册全部实现类可能不是你想要的行为。此时可以为InterfaceType传入{ autoRegisterImplementations: false }来阻止自动发射所有实现类InterfaceType({ autoRegisterImplementations: false }) abstract class Node { Field(type ID) id: string; }对应的元数据标记为autoRegisteringDisabled见 InterfaceType.ts并在 schema-generator.ts 的buildOtherTypes中被检查——只有实现了自动注册未被禁用且接口已被使用的对象类型才会被纳入autoRegisteredObjectTypesInfo。关闭自动注册后你需要把希望在指定 schema 中暴露的实现类手动加入buildSchema的orphanedTypes数组const schema await buildSchema({ resolvers, // 提供孤立的未被引用的对象类型 orphanedTypes: [Person, Animal, Recipe], });需要注意如果某个对象类型类被显式用作 GraphQL 类型例如Recipe作为addRecipemutation 的返回类型那么无论orphanedTypes如何设置它都会被发射进 schema。resolveType运行时解析具体对象类型当一个对象类型实现了 GraphQL 接口时解析器必须返回该类型类的真实实例否则graphql-js无法正确识别底层 GraphQL 类型。例如下面的查询返回IPerson[]那么数组中的每一项都必须是Student/Employee等具体类的实例仓库示例 examples/interfaces-inheritance/resolver.ts 中正是用Object.assign(new Student(), {...})创建真实实例。你也可以为InterfaceType提供自定义的resolveType函数这样解析器就可以返回普通对象plain object由你通过检查数据对象的形状来判断具体类型——这与 unions 的做法一致InterfaceType({ resolveType: value { if (grades in value) { return Student; // 返回类型的 schema 名称字符串 } return Person; // 或直接返回对象类型类 }, }) abstract class IPerson { // ... }不过接口场景比 union 稍微棘手实现该接口的对象类型可能很多你未必记得住全部因此返回类型类时请确保类已在 schema 中注册。从实现上看schema 生成器在创建GraphQLInterfaceType时会自动注入默认的resolveType它遍历所有实现该接口的对象类型用instance instanceof typeCls找到匹配类型并返回其 schema 名称若自定义了resolveType则优先使用你的实现参见 schema-generator.ts。同时仓库示例 examples/interfaces-inheritance/person/person.interface.ts 也给出了一种稳妥做法resolveType: value value.constructor.name直接依据实例的构造器名称返回 schema 类型名。完整实战示例查询返回接口类型仓库中的 examples/interfaces-inheritance 目录提供了接口与继承的完整可运行示例其结构包含person/person.interface.ts定义了IPerson接口抽象类含id、name、age字段与带size参数的avatar方法student/student.type.ts 与 employee/employee.type.ts分别继承Person的Student/Employee类型student/student.input.ts 与 employee/employee.input.ts对应的输入类型继承PersonInputresolver.ts包含返回[IPerson]的persons查询以及addStudent/addEmployee两个 mutation。核心要点Query(_returns [IPerson]) persons(): IPerson[] { return this.personsRegistry; }查询返回接口数组时GraphQL 必须能够解析每一项的具体类型因此务必返回真实类实例或提供自定义resolveType。该示例还演示了接口字段avatar由各具体类型覆写实现、输入类型沿继承链扩展字段等进阶用法是理解本文全部概念的绝佳参考。总结场景关键语法/配置说明定义接口类型InterfaceType() 抽象类 Field以抽象类充当运行时接口对象类型实现接口ObjectType({ implements: IPerson })可传单个类或数组隐式实现省略子类字段装饰器字段从接口定义复制仅靠 TS 类型检查继承接口类class Person extends IPerson父类字段自动继承进 schema接口实现接口InterfaceType({ implements: Node })需 graphql-js ≥ 15.0关闭自动注册autoRegisterImplementations: false配合buildSchema的orphanedTypes自定义类型解析resolveType选项支持返回 schema 名称字符串或类型类接口字段解析器类内方法 /FieldResolver支持Arg/Args参数声明掌握了接口定义、多接口实现、继承链合并与resolveType机制之后你就能用 TypeScript 类的形式构建出结构清晰、支持多态查询的 GraphQL schema同时把类型契约的维护成本降到最低。赞分享后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载相关推荐TypeGraphQL 接口Interface实战指南用抽象类与 InterfaceType 构建 GraphQL 接口类型TypeGraphQL 接口Interface实战指南用抽象类与 InterfaceType 构建 GraphQL 接口类型 TypeGraphQL 的后端GraphQLAPI设计TypeGraphQL 接口类型Interface Type完全指南用抽象类定义 GraphQL 接口TypeGraphQL 接口类型Interface Type完全指南用抽象类定义 GraphQL 接口 TypeGraphQL 的核心设计理念是 用 T后端GraphQLAPI设计TypeGraphQL 接口类型GraphQL Interface实战指南用抽象类与装饰器定义接口、实现继承与类型解析TypeGraphQL 接口类型GraphQL Interface实战指南用抽象类与装饰器定义接口、实现继承与类型解析 TypeGraphQL 的核心思想后端GraphQLAPI设计上一篇RetroArch音频优化终极指南告别延迟重拾游戏乐趣下一篇乳腺癌语义分割数据集BCSS终极指南三步实现精准病理图像分析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考