学分
- GraphQL模块—基于特征的GraphQL模块,规模化,由Urigo 编写
今天,我们很高兴地宣布,我们将提供过去两个月一直在生产中使用的开源框架GraphQL Modules!
是另一个框架吗?好吧,.. GraphQL模块是围绕令人惊奇的Apollo Server 2.0的一组额外的库,框架和准则。
您可以并且应该将它们作为完全独立的程序包使用,每个程序包都适合不同的用例,但是所有这些共同代表了我们当前创建大型GraphQL服务的真实理念。
我们希望得到Apollo团队的反馈,如果他们想使用这些想法并将其集成到Apollo Server中,我们将为您贡献力量。这就是为什么我们将其开发为单个monorepo上的独立工具包的原因。
GraphQL模块背后的基本概念是将GraphQL服务器分成较小的,可重用的,基于功能的部分。
GraphQL服务器的基本和初始实现通常包括:
更高级的实现通常使用上下文来注入诸如数据模型,数据源,搜索引擎等之类的内容,因为Apollo Server 2.0为我们提供了:
通常,对于简单的用例,上面的示例将适用。
但是,随着应用程序的增长,它们的原理图代码和关系变得越来越大,越来越复杂,这可能会使架构维护变得困难且难以使用。
从较旧的结构来看,MVC在解析程序层之后增加了几层,但是大多数只实现基于分离的技术层:控制器,模型等。
我们认为,有一种更好的方法来编写GraphQL模式和实现。
我们认为您应该按模块或功能分开GraphQL模式,并在“模块”(这只是一个简单的目录)中包括与应用程序特定部分有关的所有内容。每个GraphQL模块库都将帮助您逐步进行此操作。
模块是由GraphQL模式定义的-因此,我们采用了Apollo主导的“ GraphQL First”方法,并将其与经典的模块化工具相结合,以创建新的方式来编写GraphQL服务器!
GraphQL模块工具集提供的工具可帮助:
- 模式分离 -在较小的部分声明GraphQL模式,以后可以移动和重用。
- 用于创建独立模块的工具 -每个模块都是完全独立且可测试的,可以与其他应用程序共享,甚至在需要时可以与开源共享。
- 解析器组成 -使用GraphQL模块,您可以根据需要编写解析器,然后让应用程序托管模块将解析器分组在一起并扩展它们。这是通过基本的中间件API实现的,但具有更大的灵活性。这意味着,例如,您可以在不知道应用程序身份验证过程的情况下部署整个模块,并假定currentUser将由应用程序注入。
- 一条清晰且循序渐进的路径 -非常简单,快速的单个文件模块可扩展到多个文件模块,多个团队,多个存储库和多个服务器。
- GraphQL服务器的可伸缩框架 -管理多个团队和资源,多个微服务和服务器。
您可以选择在架构大规模使用时包括的更高级的工具:
- 通讯桥 -我们还允许您在模块之间发送自定义的有效消息-这意味着您可以在不同的微服务上运行模块并轻松地在它们之间进行交互。
- 依赖注入 -实现您的解析器,然后仅在您认为有必要时才通过逐步引入依赖注入来改进其实现。还包括一个用于测试和模拟的丰富工具包。
一个实际的例子
在以下示例中,您可以看到GraphQL Modules服务器的实际实现,其中包含2个模块:User和Chat。
每个模块仅声明与其相关的部分,并扩展先前声明的GraphQL类型。
因此,在加载用户模块时,将创建用户类型,在加载聊天模块时,将使用更多字段扩展用户类型。
我server.js:
import { AppModule } from './modules/app-module';
import { ApolloServer } from 'apollo-server';
const { schema, context } = AppModule;
const server = new ApolloServer({
schema,
context,
introspection: true,
});
server.listen();
Em app-module.js:
import { GraphQLModule } from '@graphql-modules/core';
import { UserModule } from './user-module';
import { ChatModule } from './chat-module';
export const AppModule = new GraphQLModule({
imports: [
UserModule,
ChatModule,
],
});
Em chat-module.js:
import { GraphQLModule } from '@graphql-modules/core';
import gql from 'graphql-tag';
export const ChatModule = new GraphQLModule({
typeDefs: gql`
# Query é declarada novamente, adicionando apenas a parte relevante
type Query {
myChats: [Chat]
}
# User é declarado novamente, estendendo qualquer outro tipo de `User` carregado no `appModule`
type User {
chats: [Chat]
}
type Chat {
id: ID!
users: [User]
messages: [ChatMessage]
}
type ChatMessage {
id: ID!
content: String!
user: User!
}
`,
resolvers: {
Query: {
myChats: (root, args, { getChats, currentUser }) => getChats(currentUser),
},
User: {
// Este módulo implementa apenas a parte do `User` que ele adiciona
chats: (user, args, { getChats }) => getChats(user),
},
},
});
Em user-module.js:
import { GraphQLModule } from '@graphql-modules/core';
import gql from 'graphql-tag';
export const UserModule = new GraphQLModule({
typeDefs: gql`
type Query {
me: User
}
# Este é o User inicial, com apenas o mínimo de um objeto de usuário
type User {
id: ID!
username: String!
email: String!
}
`,
resolvers: {
Query: {
me: (root, args, { currentUser ) => currentUser,
},
User: {
id: user => user._id,
username: user => user.username,
email: user => user.email.address,
},
},
});
您可以并且应该部分采用GraphQL模块,现在可以尝试使用现有的GraphQL服务器。
“模块”包含什么?
- 模式(类型声明) -每个模块都可以定义自己的模式并扩展其他模式类型(无需显式提供它们)。
- 实现隔离的解析器 -每个模块都可以实现自己的解析器,从而生成小的隔离的解析器,而不是大型文件。
- 提供程序 -每个模块都可以有自己的提供程序,它们只是可以在解析程序中使用的类/值/函数。模块可以加载和使用其他模块的提供程序。
- 配置 -每个模块都可以声明消费者应用程序可以提供的强类型配置对象。
- 依赖关系 -模块可能依赖于其他模块(通过名称或GraphQLModule的实例,因此您可以轻松创建模棱两可的依赖关系,以后可以更改)。
GraphQL模块库
GraphQL模块是使用以下工具构建的工具包,您必须逐步逐步采用它们:
@ graphql-modules /环氧树脂
- 这可能是您要在服务器上引入的第一个工具。将服务器组织到基于资源的框架中的第一步。
- 环氧树脂是管理架构合并的小型实用程序。使您可以将所有内容合并到架构中,从类型开始,直至枚举,联合,指令等。
- 这是GraphQL模块的重要功能-您可以使用它将GraphQL类型分成较小的部分,然后将它们组合为单个类型。
- 我们从merge-graphql-schemas中汲取了灵感,并添加了一些功能以允许自定义合并规则使架构分离更加容易。
@ graphql-modules / core
- 解析器组成 -管理打包应用解析器
- 建立上下文 -每个模块都可以将自定义属性注入到架构中,其他模块可以使用它(例如,auth模块可以注入当前用户,其他模块可以使用它)
- 依赖注入和模块依赖管理 -开始时,无需在服务器上使用DI,但是当服务器变得足够大且包含大量相互依赖的模块时,DI才会使用极大地帮助了您,大大简化了您的代码。仅在必要时使用 ;)
您可以找到更多可用的工具,例如:
- @ graphql-modules / sonar-一个小的实用程序,可以帮助您找到GraphQL模式和解析器文件并将其包括在内。
- @ graphql模块/记录器 -一个小记录器,总部设在温斯顿3,你可以在应用程序中轻松使用。
脚步声
首先,从简单开始!通过使用现有工具将代码移动到基于资源的文件夹和结构中来开始使用。
然后访问https://graphql-modules.com/并检查我们的工具,仅在您意识到它们可以为您解决实际问题时使用它们!
还要检查资源库README和各种示例应用程序。
您可能有很多问题-与其他工具相比如何,如何在X中使用这些库,等等。
在接下来的几周中,我们将发布一系列帖子,这些帖子将深入解决所做的每个设计决策。因此,我们想听听您的想法和问题。请在这里或在Github仓库中发表评论!
要参加GraphQL峰会吗?我将在那里,并代表我们的团队回答您的问题和意见。
所有这些工具都是由一群热忱的个人开源开发人员(也称为The Guild)构建的。
以下是更深入的思想部分,我们将在接下来的几周中分别发表文章:
主要概念和高级潜水
调制方案
每个人都在谈论架构拼接和GraphQL绑定。这在哪里适合图像?
模式缝合是一项了不起的技能和概念,可帮助将单独的GraphQL服务器合并到单个端点中,并打开了许多有趣的用例。
但是令人兴奋的是,我们失去了比这更重要的东西-有时我们仍然想在单个逻辑服务器上工作,但是我们只想根据资源分离代码。
我们希望能够在构建时完成大部分合并工作,并且只有在确实必要时,才在运行时进行其余的合并。
我们希望将代码分成不同的团队,甚至创建可重用的模块,以通过GraphQL模式定义其外部API。
这些模块可以是npm模块,微服务,也可以是单个服务器中的单独文件夹。
当您处理typeDef和解析器时,将模式分成较小的部分会更加容易,从而使其更易读和易于理解。
我们还希望允许开发人员仅扩展特定类型,而不创建整个架构。使用GraphQL模式,您需要至少指定一个Query类型的字段,我们不想将其强加给我们的用户。
我们认为我们的方法是对模式缝合的补充和配合。
基于资源的部署
GraphQL模块方法中最重要的事情之一是基于资源的实现。
如今,大多数结构都根据层的角色(例如控制器,数据访问等等)将层分开。
GraphQL模块采用不同的方法-它根据服务功能分离模块,并允许您在每个模块实现中管理自己的层。
以模块化的方式考虑应用程序比较容易,例如:
您惊人的应用程序需要身份验证,用户管理,用户个人资料,用户图库和聊天。
每个模块都可以是一个模块,并且可以实现自己的GraphQL模式和逻辑,并且可以依赖其他模块来提供某些逻辑。
这是所描述的GraphQL模式的示例:
但是,如果我们从资源的角度考虑应用程序,然后按模块将模式分开,则模块的分离将如下所示:
这样,每个模块只能声明其贡献的架构的一部分,而完整的架构则表示所有合并的类型定义。
该模块还可以依赖,导入,扩展和自定义其他模块的内容(例如,User模块随附有Auth)。
结果当然是一样的,因为我们将模式合并为一个,但是代码库将更加有条理,并且每个模块都有自己的逻辑。
重用后端模块
因此,既然我们了解了基于资源的实现的强大功能,那么更容易理解代码重用背后的思想。
如果我们可以将“身份验证和用户”模块的架构以及主要部分实现为“即插即用”,则可以稍后以很小的更改(使用配置,依赖项注入或模块组成)将其导入到其他项目中。
我们如何重用包含模式一部分的完整模块?
例如,让我们使用User类型。
大多数用户类型方案将包含id,email和username字段。突变类型将具有登录名,而查询将具有用于查询特定用户的用户字段。
我们可以重用此类型声明。
应用程序之间的实际实现可能会有所不同,具体取决于身份验证提供程序,数据库等,但是我们仍然可以在解析器中实现业务逻辑,使用依赖项注入器,并要求应用程序使用模块。提供实际的身份验证功能(当然,具有完整的TypeScript接口,我们将知道需要提供该功能;)。
让我们更进一步。如果要向用户添加个人资料图片,可以添加一个名为UserProfile的新模块,然后重新声明User和Mutation类型:
type User {
profilePicture: String
}
type Mutation {
uploadProfilePicture(image: File!): User
}
这样,GraphQL模块会将这种类型的User字段合并为完整的User类型,并且该模块将仅通过必要的操作扩展User类型和Mutation类型。
因此,假设我们拥有模式-如何使该模块通用并重用它?
这是您声明此模块的方式:
import { GraphQLModule } from '@graphql-modules/core';
import gql from 'graphql-tag';
import { UserModule } from '../user';
import { Users } from '../user/users.provider';
export interface IUserProfileModuleConfig {
profilePictureFields ?: string;
uploadProfilePicture: (stream: Readable) => Promise<string>;
}
export const UserProfileModule = new GraphQLModule<IUserProfileModuleConfig>({
imports: [
UserModule,
],
typeDefs: gql`
type User {
profilePicture: String
}
type Mutation {
uploadProfilePicture(image: File!): User
}
`,
resolvers: config => ({
User: {
profilePicture: (user: User, args: never, context: ModuleContext) => {
const fieldName = config.profilePictureField || 'profilePic';
return user[fieldName] || null;
},
},
Mutation: {
uploadProfilePicture: async (root: never, { image }: { image: any }, { injector, currentUser }: ModuleContext) => {
// usando https://www.apollographql.com/docs/guides/file-uploads.html
const { stream } = await image;
// Obtenha o método externo para fazer upload de arquivos, isso é fornecido pelo aplicativo como config
const imageUrl = config.uploadProfilePicture(stream);
// Obtém o nome do campo
const fieldName = config.profilePictureField || 'profilePic';
// Peça ao injetor o token "Users", estamos assumindo que o módulo `user` o expõe para nós,
// e atualize o usuário com a URL enviado.
injector.get(Users).updateUser(currentUser, { [fieldName]: imageUrl });
// Retorne o usuário atual, podemos assumir que `currentUser` estará no contexto devido
// a composição dos resolvedores - explicaremos mais adiante.
return currentUser;
},
},
}),
});
我们声明一个配置对象,应用程序将提供它,以便我们以后可以用其他上载逻辑替换它。
扩展代码库
现在我们已经将应用程序划分为各个模块,随着代码库的增长,我们可以分别扩展每个模块。
调整代码库是什么意思?
假设我们开始要在不同模块之间共享代码。
在GraphQL的现有世界中,实现此目的的当前方法是通过GraphQL上下文。
这种方法已被证明行之有效,但是在某个时候它变得非常麻烦,因为GraphQL上下文是应用程序的任何部分都可以修改,编辑和扩展的对象,并且可以迅速变得很大。
GraphQL模块允许每个模块将字段扩展并将其注入到上下文对象中,但这是您应谨慎使用的方法,因为我建议上下文包含实际的上下文 -包含诸如全局配置,环境,当前用户和以此类推。
GraphQL模块在上下文中仅添加一个字段称为注入器,这是使您可以访问GraphQLApp和注入器应用程序以及获取模块的配置和提供程序的桥梁。
模块可以是项目中的简单目录,monorepo或已发布的NPM模块-您可以根据自己的需要和偏好选择如何管理代码库。
依赖注入
GraphQL Modules依赖项注入是受.NET和Java依赖项注入启发的,这些事实已证明可以使用多年。话虽如此,我们试图列出和解析.NET和Java API的一些问题。我们得出了一些非常有趣的结论。
我们了解到,这不是必须强制执行的事情。在某些特定的用例中,依赖注入是有意义的,并且仅应在需要时使用它。因此,随着我们扩大规模,简化工作,轻松维护代码并管理团队的贡献,这一概念将越来越有用。
我们在所有公司客户上都使用GraphQL模块,同时也在较小的应用程序上使用它们,这使我们相信,我们已经找到了使用依赖项注入概念的理想点,何时不使用它。
我们还找到了用于依赖项注入的理想API。这是非常容易理解和使用的。
在对现有的JavaScript依赖注入解决方案进行了广泛研究之后,我们决定实现一个简单的Injector,该注入器可满足GraphQL Modules生态系统的需求,支持循环依赖等。
我们简化了依赖项注入API,仅向您公开了我们认为开发GraphQL服务所需的重要部分。
认证方式
看看我们为此写的帖子:
测试和存根
在我们的应用程序中,当我们开始使用依赖项注入时,我们不再需要管理实例并合并它们。
我们获得了一个抽象,使我们可以更轻松地测试事物并从所有http请求进行模拟。
是的,嘲笑。DI真的很闪亮。
多亏了模拟,我们可以模拟许多场景并将后端与它们进行比较。
并且随着代码库的增长,您需要开始考虑管理模块之间的依赖关系以及如何避免循环依赖之类的东西-除非您使用DI可以为您解决此问题。
借助依赖关系注入的功能,您可以轻松地在模块之间创建连接,并使该连接基于令牌和TypeScript接口。
这也意味着测试要容易得多-您可以将类/函数作为独立单元进行测试,并轻松模拟其依赖关系。
精加工
我们了解GraphQL Modules框架的工作原理,如何从头开始构建,以及GraphQL和Apollo的令人兴奋的新功能,将它们与良好的旧软件最佳实践正确地结合起来,以进行模块化,典型化扩展强注入和依赖注入。
现在去尝试-https: //graphql-modules.com/
所有文章均支持GraphQL模块
- GraphQL模块—大规模基于特征的GraphQL模块
- 为什么在大型GraphQL项目中真正的模块化封装如此重要?
- 为什么我们为GraphQL-Modules实现自己的依赖注入库?
- GraphQL-Modules依赖注入中的范围提供者
- 使用GraphQL-Modules和GraphQL-Code-Generator编写GraphQL TypeScript项目
- GraphQL中的身份验证和授权(以及GraphQL-Modules如何提供帮助)
- 使用AccountsJS和GraphQL模块进行身份验证
- 使用GraphQL-Modules管理循环导入地狱