🍬

GraphQL界のbabel、Envelopとは何か

2021/12/04に公開

この記事は この記事は GraphQL Advent Calendar 2021 2日目の記事です。

今回は、Graphql Code Generator等GraphQL界隈のライブラリを多く手掛けるGuild社が現在最も力を入れているライブラリあるEnvelopについて紹介したいと思います。

Envelopとは

The missing GraphQL plugin system.

公式ドキュメント

graphql関連のOSSを多数手がけるGuild社によって作られた、新しいGraphQL用のプラグインシステムです。Apollo ServerやのようなGraphQLサーバーレイヤーではなく、graphql engineそのもののラッパーライブラリとなっていて、GraphQLの各実行フェーズ(parse-validate-execute)やcontextBuilding等のタイミングで任意の処理をhookするpluginを仕込むことで拡張ができる仕組みになっています。
プラグインシステムに加え、@defer/@stream, @live, oneOfのようなgraphqlの新しい仕様をEnvelopを通すことで利用可能にするというところも含め、Babel for GraphQLを目指したライブラリとなっています。
また、プラグインの命名規則がuseXXというところからはReact Hooksの影響も見ることができ、フロントエンドのエコシステムをバックエンドGraphQLへ持ってきた感があります。

まだ新しいライブラリでベータっぽいところもありますが、野心的なライブラリとなっています。

最小限の実装例(Apollo Server)

apollo serverでの最小限の実装例です。httpのみのシンプルな実装でも良いのですが、最も馴染みのありそうなApollo Serverを選びました。ただし、apollo serverはparseフェーズの拡張ができないためenvelopとの相性は現状微妙ではあります。

import { ApolloServer } from 'apollo-server';
import { envelop, useSchema, useTiming } from '@envelop/core';
import { makeExecutableSchema } from '@graphql-tools/schema';

const schema = makeExecutableSchema({
  typeDefs: /* GraphQL */ `
    type Query {
      hello: String!
    }
  `,
  resolvers: {
    Query: {
      hello: () => 'World',
    },
  },
});

const getEnveloped = envelop({
  plugins: [useSchema(schema), useTiming()],
});

const server = new ApolloServer({
  executor: async requestContext => {
    const { schema, execute, contextFactory } = getEnveloped({ req: requestContext.request.http });

    return execute({
      schema: schema,
      document: requestContext.document,
      contextValue: await contextFactory(),
      variableValues: requestContext.request.variables,
      operationName: requestContext.operationName,
    });
  },
});

server.listen(3000);

Plugin定義

envelopの肝であるplugin定義です。

const getEnveloped = envelop({
  plugins: [
    useSchema(mySchema),
    useLogger(),
    useTiming()
  ],
});

pluginsオプションにpluginの配列を渡すことでプラグイン定義ができます。webpack等と同じく順番には意味があり、上から下の順で実行されていくので別のpluginが提供するcontextに依存する場合は順番を間違えると動かなくなるので注意が必要です。

envelopの旨味

ライブラリ作者として

envelopは、graphqlそのものの機能拡張という側面からgraphqlサーバーフレームワークのためのツールとして効果を発揮できます。一例として、Redwoodjsはguild社とコラボレーションしてapolloベースだったgraphqlサーバーをgraphql-helix + envelopに置き換えました。
https://community.redwoodjs.com/t/using-graphql-envelop-helix-in-redwood-v0-35/2276

アプリケーションエンジニアとして

フレームワークを作らずとも、アプリケーションを作るにあたって「よくあるサーバー処理」は何度も実装したいものではありません。envelopのプラグインシステムはreact hooksのようによくあるロジックをplugin化して使い回すことができます。例えばcustom directiveなどはenvelopのpluginとして再利用可能なものにすることが出来ます。

plugin hub

pluginは既に数多く揃っていて、plugin hubから見つけることができます。

いくつかピックアップしてみます:

  • useLogger: シンプルにサーバーログを出力してくれるplugin
  • useAuth0: よくあるauth0のjwtを検証してuser情報をcontextに詰めるやつをやってくれるplugin
  • useGenericAuth: 認証関連のビジネスロジックをinjectできるplugin
  • useSentry: エラー監視ツールのSentryとの連携が簡単にできるplugin

envelopと組み合わせるGraphQL Server

envelopはframework agnosticなのでどんなgraphql serverにも使うことが出来ます。ただ、フレームワークによってはすでに多機能であったり、graphql-jsの拡張が出来ない仕組みになっているものもありenvelopとの相性が悪いものもあります。例えば、Apollo Serverは前述の通り、parseフェーズの拡張をサポートしていないのであまり良い組み合わせとは言えないです。

envelopを中心に据えるのであれば、envelop公式でも推奨のgraphql-hielixを利用するのが今のところ良さそうです。また、graphql-helixのメンテナのほとんどがenvelopのコントリビューターです。

まだ発展途中。だがそれがいい

envelopは野心的なライブラリですが、まだ発展途上にあります。自分は個人ボイラープレートでgraphql-helixとenvelopを組み合わせてみましたが、少し凝ったことをしようとすると動かず、いくつかissueを立てたりPR出したりして動くようにしました。出て間もないライブラリを使うのは人柱的な側面もありつつ、ライブラリに貢献しながら使っていく面白さもあります。

envelop + graphql-helixを使ったボイラープレート

紹介したenvelopとgraphql-helixを使ってボイラープレートを作ってみました。envelop pluginの実装例として、ownerチェックをするカスタムディレクティブ@isOwner(ownerField: String)を実現するuseOwnerCheckを作っていたりします。Nextjsのフロントエンドも含めたフルスタックなgraphqlボイラープレートになっているので、よかったら見てみてください。
https://github.com/taneba/fullstack-graphql-app

以上、graphql界のbabelを目指したenvelopの紹介でした。

Discussion