【GraphQL】需要掌握的关键点来实现GraphQL

前言

由于接触GraphQL,我开始意识到一些需要思考的要点。由于这是一个总结要点的文章,所以代码并不多。

对于前半部分,即使不熟悉GraphQL也可以理解,但是后半部分关于认证和授权的内容会更加复杂,所以建议先学习一些GraphQL然后再阅读可能会更好。

GraphQL的特点

如果你对GraphQL的特点了解得差不多的话,请跳过这一部分。

    1. 只有一个终端点

 

    1. 有两种查询:用于引用的query和用于添加和更新的mutation

 

    根据发送查询中的数据类型(模式),可以在一个请求中按照类型(模式)获取所需的数据一次。

优点

从这个特点中获得的好处是,即使不深思熟虑也可以完成简单的API。可以轻松实现无需不必要的递归请求的设计,这在当前主流的“REST API”中经常发生。如果在实施后需要递归请求,意味着在GraphQL中可以立即进行扩展,而不会造成破坏性的变更。

换句话说,我们可以轻松地创建一个具备扩展性和简洁性,能够在一次请求中获取所需信息的API。

弊端

作为缺点,“追加/更新查询mutation”需要我们自己在这一端进行处理,与“REST API”相同。具体来说,这个mutation查询需要决定将“ほげほげ”值注册到哪个表的哪条记录中。其次,无法简单地引入认证和授权,引入方法也没有明确。最后,还没有确定一个地方来执行仅特定查询的特殊处理,而且GraphQL本身也不应该包含特殊处理。特殊处理包括将图像数据保存到AWS的S3中,并将其URL存储在数据库中等。

换句话说,一旦考虑创建变异查询、实施认证和授权、特殊处理,GraphQL的可扩展性和简洁性就会受到影响。

设计指导方针

在GraphQL设计中,重要的是要保留好处并尽可能消除缺点。

不要將特殊處理包括在突變中,盡量設計成無需含有該處理。然而,身份驗證和授權是不可或缺的,因此將其作為GraphQL功能添加。

为了保持GraphQL的简洁性,我们准备了另一个API(REST API?),通过GraphQL查询来仅传递与数据库存储相关的数据,并进行追加和更新。虽然这样需要发送两次请求,但考虑到GraphQL的缺点,我们还是认为将其拆分更为适合。

graphql_01.png

在这里剩下的担忧是关于认证和授权应该如何实施。

認證和授權的實施方法

这篇文章非常有参考价值,这里所解释的内容基本上就像是这个地方的总结。

对所有查询进行身份验证

在实施GraphQL服务器上添加中间件,并在中间件中添加身份验证处理程序,就可以为所有查询添加身份验证。如果身份验证在中间件中失败,可以执行查询并返回错误。但是这样无法为每个查询添加身份验证。

对每个查询进行身份认证和授权

使用解析器进行认证和授权检查

解析器是根据查询的类型(模式)创建返回数据的位置。
在解析器内,将返回数据的部分包装在认证和授权函数中。

由于解析器是根据查询的类型(模式)生成返回数据的地方,因此我认为在这里编写身份验证和授权处理并不太好。这并不是推荐的做法。

承認的例子

const checkScopeAndResolve = (context, expectedScope, controller) => {
  const token = context.headers.authorization;

  try {
    // 認証・認可処理
    // ...

     return controller.apply(this);
  } catch (err) {
    // 認証・認可処理失敗時の処理
  }
}

const controller = model.getArticles(context.user.id);

const resolvers = {
  Query: {
    articlesByAuthor: (_, args, context) 
      => checkScopeAndResolve(
        context.user.scope,
        ['read:articles'],
        controller
      );
  }
}
2. 使用指令检查认证和授权。

指令(Directive)是一种可以为数据类型(模式)或查询添加元数据的东西。
通过为查询添加认证和授权指令,可以声明该查询需要认证和授权。
然后,在执行查询之前,可以运行自定义的认证和授权指令处理。

实施指令虽然有些困难,但不会影响解析器,所以这是最推荐的选项。

身份验证的例子

import { SchemaDirectiveVisitor } from "graphql-tools"
import { DirectiveLocation, GraphQLDirective, GraphQLList, GraphQLString } from "graphql"
import { AuthorizationError } from '../errors'

export class IsAuthenticatedDirective extends SchemaDirectiveVisitor {
  static getDirectiveDeclaration(directiveName, schema) {
    return new GraphQLDirective({
      name: "isAuthenticated",
      locations: [DirectiveLocation.FIELD_DEFINITION]
    });
  }

  visitFieldDefinition(field) {
    const { resolve = defaultFieldResolver } = field
    field.resolve = async (...args) => {
      const ctx = args[2]
      if (!ctx || !ctx.req || !ctx.req.headers || 
 !ctx.req.headers.authorization) {
        throw new AuthorizationError({ message: "No authorization token." })
      }
      const token = ctx.req.headers.authorization

      try {
        const id_token = token.replace("Bearer ", "")

        // authorization process
        // ...

        return resolve.apply(this, args)
      }
      catch (e) {
        throw new AuthorizationError({ message: "You are not authorized." })
      }
    }
  }


}

附言

为了使GraphQL易于处理,建议尽量简化实现,不包含身份验证和权限控制。

bannerAds