GraphQL: Serviços em larga escala com GraphQL Modules

Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
  • Ferramentas projetadas para criar módulos independentes — cada módulo é completamente independente e testável e pode ser compartilhado com outros aplicativos ou até de código aberto, se necessário.
  • Composição dos resolvedores — com GraphQL Modules, você pode escrever o seu resolvedor como desejar e deixar o aplicativo que hospeda o módulo agrupar os resolvedores e estendê-los. Isso é implementado com uma API básica de middleware, mas com maior flexibilidade. Isso significa que você pode, por exemplo, implementar todo o seu módulo sem conhecer o processo de autenticação do aplicativo e assumir que currentUser que será injetado pelo aplicativo.
  • Um caminho claro e gradual — módulos muito simples e rápidos de arquivo único a módulos escaláveis ​​de múltiplos arquivos, múltiplas equipes, múltiplos repositórios e múltiplos servidores.
  • Uma estrutura escalável para seus servidores GraphQL — gerenciando várias equipes e recursos, vários micros-serviços e servidores.
  • Injeção de Dependência — Implemente seus resolvedores e, posteriormente, somente quando achar necessário, melhore a implementação deles, introduzindo gradualmente a injeção de dependência. Também é incluído um conjunto de ferramentas ricas para testes e mocks.

Um exemplo prático

No exemplo a seguir, você pode ver uma implementação prática para um servidor GraphQL Modules, com 2 módulos: User e Chat. Cada módulo declara apenas a parte que é relevante para ele e estende os tipos do GraphQL declarados anteriormente.

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();
import { GraphQLModule } from '@graphql-modules/core';
import { UserModule } from './user-module';
import { ChatModule } from './chat-module';
export const AppModule = new GraphQLModule({
imports: [
UserModule,
ChatModule,
],
});
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),
},
},
});
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,
},
},
});
  • Implementação de resolvedores isolados — cada módulo pode implementar seus próprios resolvedores, resultando em resolvedores pequenos e isolados, ao invés de arquivos gigantes.
  • Provedores — cada módulo pode ter seus próprios provedores, que são apenas classes / valores / funções que você pode usar dos seus resolvedores. Os módulos podem carregar e usar provedores de outros módulos.
  • Configuração — cada módulo pode declarar um objeto de configuração fortemente tipado, que o aplicativo consumidor pode fornecer.
  • Dependências — os módulos podem depender de outros módulos (pelo nome ou pela instância do GraphQLModule, para que você possa criar facilmente uma dependência ambígua que posteriormente poderá ser alterada).

Bibliotecas do GraphQL Modules

O GraphQL Modules é construído como um kit de ferramentas, com as seguintes ferramentas, que você deve adotar individualmente e gradualmente:

@graphql-modules/epoxy

  • Essa provavelmente será a primeira ferramenta que você deseja introduzir no seu servidor. O primeiro passo para organizar seu servidor em uma estrutura baseada em recursos
  • Epoxy é um pequeno utilitário que gerencia a mesclagem de esquema. permite mesclar tudo em seu esquema, começando com tipos até enumerações, uniões, diretivas e assim por diante.
  • Esse é um recurso importante do GraphQL Modules — você pode usá-lo para separar seus tipos GraphQL em partes menores e, posteriormente, combiná-los em um único tipo.
  • Tiramos a inspiração do merge-graphql-schemas e adicionamos alguns recursos para permitir regras de mesclagem personalizadas para facilitar a separação do esquema.

@graphql-modules/core

  • Composição dos resolvedores — gerencia a empacota os resolvedores do aplicativo
  • Construção de contexto — cada módulo pode injetar propriedades personalizadas no esquema e outros módulos podem usá-lo (por exemplo, o módulo auth pode injetar o usuário atual e outros módulos podem usá-lo)
  • Injeção de dependência e gerenciamento de dependências do módulo — quando você inicia, não há necessidade de usar o DI em seu servidor, mas quando seu servidor fica grande o suficiente com um grande número de módulos que depende um do outro, somente então o DI se torna uma coisa de grande ajuda que realmente simplifica bastante o seu código. USE SOMENTE QUANDO NECESSÁRIO ;)
  • @graphql-modules/logger — um pequeno logger, baseado no winston 3, que você pode usar facilmente em seu aplicativo.

Passo a Passo

Primeira coisa, comece simples! Comece movendo seu código para pastas e estruturas baseadas em recursos com as ferramentas existentes.

Conceitos principais e um mergulho avançado

Modularizando um esquema

Todo mundo está falando sobre costurar esquemas (schema stitching) e GraphQL Bindings. Onde isso se encaixa na imagem?

Implementação baseada em recursos

Uma das coisas mais importantes na abordagem do GraphQL Modules é a implementação baseada em recursos.

Image for post
Image for post
Image for post
Image for post

Reutilização de módulos do backend

Portanto, agora que entendemos o poder da implementação baseada em recursos, é mais fácil entender a ideia por trás da reutilização de código.

type User {
profilePicture: String
}
type Mutation {
uploadProfilePicture(image: File!): User
}
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;
},
},
}),
});

Escalando a base de código

Agora que dividimos nosso aplicativo em módulos individuais, ao modo que nossa base de código cresce, podemos dimensionar cada módulo individualmente.

Injeção de dependência

A injeção de dependência do GraphQL Modules é inspirada na injeção de dependência do .NET e Java, que provou funcionar ao longo dos anos. Com isso dito, houve alguns problemas com as APIs do .NET e Java, que tentamos listar e analisar. Tivemos algumas conclusões bastante interessantes.

Autenticação

Confira a postagem que escrevemos sobre isso:

Testes e Stubs

Em nossos aplicativos, quando começamos a usar a injeção de dependência, não era mais necessário gerenciar instâncias e uni-las.

Finalizando

Entendemos como a estrutura do GraphQL Modules funciona, como ela foi construída a partir do zero, com os novos e empolgantes recursos do GraphQL e Apollo, combinando-os da maneira correta com as boas e antigas práticas recomendadas de software para dimensionar com modularizações, tipificações fortes e injeção de dependência.

Todos os artigos sbore GraphQL Modules

  1. GraphQL Modules — Feature based GraphQL Modules at scale
  2. Why is True Modular Encapsulation So Important in Large-Scale GraphQL Projects?
  3. Why did we implement our own Dependency Injection library for GraphQL-Modules?
  4. Scoped Providers in GraphQL-Modules Dependency Injection
  5. Writing a GraphQL TypeScript project w/ GraphQL-Modules and GraphQL-Code-Generator
  6. Authentication and Authorization in GraphQL (and how GraphQL-Modules can help)
  7. Authentication with AccountsJS & GraphQL Modules
  8. Manage Circular Imports Hell with GraphQL-Modules

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store