LoginSignup
4
2

More than 5 years have passed since last update.

AppSyncとGithub APIをマージして一つのGraphQL APIを作る

Last updated at Posted at 2019-01-14

目的

前回記事でAppSyncによりAPIを簡単に作りましたが、今度は既存のGraphQL APIと結合させ一つのAPIにしたいです
既存APIとして今回はGithub API v4を取り上げ、AppSyncのAPIと結合させてみます。

APIの結合のユースケース

次のケースを考えます。
AppSyncには(1)自分のお気に入りの組織一覧が入っており、Githubには(2)その組織ごとのレポジトリの一覧があります。
両者を結合させたAPIでは、(1)お気に入りの組織に関係する(2)Githubのレポジトリの一覧を取得することにします

アーキテクチャ

AppSyncとGithubから両者のAPIを取り込むNode.jsサーバを起動し、両者を結合させ新たなGraphQL APIを作ります
結合させるGraphQLの仕組みを「スキーマスティッチング」といい、別記事に詳述しています。
必要なNode.jsサーバはステートレスで簡単なものですので、NetlifyやHeroku、AWS Lambdaなどのマネージドサービス上に構築すればいいです。
今回はAWS Lambdaに起動します。

image.png

AppSync側のAPIの構築

AppSync上に作成するAPI「お気に入りの組織」のモデルは以下とします。

type FeaturedOrganizations {
    id: ID!
    name: String
    github_name: String # 後ほど出てくるGithub APIの引数
}

以下のように、AppSyncのコンソールでモデルを構築します。

image.png

AppSyncのQueriesやDynamoDBの直接操作で、あらかじめいくつかのデータを登録しておきます。
私は以下のようにしました。

image.png

APIアクセスの準備

作成したAPIのサマリ画面からaws-exports.jsをダウンロードします。

image.png

GithubのAPI

Github API v4のモデルの一例に、以下のようなOrganizationがあります。
引数がloginという文字列で、返り値としてOrganizationを取得できます。

Organizationには、repositoriesというその組織のレポジトリを取得できるフィールドがあります。

type Query {
    organization(login: String!): Organization
}
type Organization {
        repositories(
            privacy: RepositoryPrivacy
            orderBy: RepositoryOrder
            affiliations: [RepositoryAffiliation]
            ownerAffiliations: [RepositoryAffiliation]
            isLocked: Boolean
            after: String
            before: String
            first: Int
            last: Int
            isFork: Boolean): RepositoryConnection!
}

image.png

スキーマはGithub API Explorerで確認できます。

APIアクセスの準備

githubのsettings -> developers settings -> personal access tokenから、アクセストークンをあらかじめ取得します。

取得したトークンを、.envに記載します。

GITHUB_ACCESS_TOKEN=<取得したトークン>

新APIの仕様とAWS Lambda上のNode.jsサーバの実装

Github APIのorganizationの引数loginに先ほどのAppSync APIのgithub-nameを入れることができます。
これにより、AppSync APIのgithub-nameを使って両者を結合したAPIを作成できます。

新APIに対するクエリは全体でこうなります。

query {
  # AppSyncをベースとした新規API
  listFeaturedOrganizations {
    items {
      name
      github_name
      # 以上がAppSync API(FeaturedOrganizations)から引き継いだ内容
      # 以下がGithub API(Organization)から引き継いだ内容
      githubOrganization {
        repositories(first: 1) {
          nodes {
            name
          }
        }
      }
    }
  }
}

新APIは、以下の図のようにAppSync API (FeaturedOrganizations)をベースとして、Github API (organization)の返り値を入れ子に持つ構造になります。

image.png

APIの返り値は以下のように作られます。
- 1) AppSyncから値をコピーする
- 2) github_nameはGithub APIの引数として使われる
- 3) organizationは新たなフィールドgithubOrganizationとして埋め込まれる

さて、結合APIの実装であるNode.jsサーバは以下のように実装します。
同じフォルダに先ほどのAppSyncからダウンロードしたaws-exports.jsを格納しておきます。

index.js
import {makeRemoteExecutableSchema, mergeSchemas, introspectSchema} from 'graphql-tools';
import fetch from 'node-fetch';
import { HttpLink } from 'apollo-link-http';
import { ApolloServer, gql } from "apollo-server-express";
import serverless from "serverless-http";
import express from "express";
import { config } from 'dotenv';
config()

const aws_exports = require('./aws-exports').default;
const github_url = 'https://api.github.com/graphql';

const createSchema = async () => {
    const createRemoteSchema = async (uri, headers) => {
        const link = new HttpLink({uri, fetch, headers});
        return makeRemoteExecutableSchema({
            schema: await introspectSchema(link),
            link
        });
    };

    const appsyncSchema = await createRemoteSchema(
        aws_exports.aws_appsync_graphqlEndpoint,
        {'X-Api-Key': aws_exports.aws_appsync_apiKey}
        );

    const githubSchema = await createRemoteSchema(
        github_url,
        { Authorization: `bearer ${process.env.GITHUB_ACCESS_TOKEN}`}
        );
    const schema = mergeSchemas({
        schemas:[githubSchema, appsyncSchema, linkSchemaDefs],
        resolvers: {
            FeaturedOrganizations: {
                githubOrganization: {
                    fragment: `fragment git on FeaturedOrganization {github_name}`,
                    resolve(parent, args, context, info) {
                        return info.mergeInfo.delegateToSchema({
                            schema: githubSchema,
                            operation: 'query',
                            fieldName: 'organization',
                            args: {login: parent.github_name},
                            context,
                            info
                        });
                    }
                }
            }
        }
    });
    return schema
}

const createServer = (schema) => {
    const app = express();
    const server = new ApolloServer({ schema });
    server.applyMiddleware({ app });
    return serverless(app);
};

let schema
let sls
exports.graphqlHandler = async (event, context) => {
    if(sls == null) {
        schema = await createSchema();
        sls = createServer(schema);
    } else {
        console.log("Already initialized")
    }
    return await sls(event, context);
}

serverlessを使ってAWS Lambdaにデプロイするために、serverless.ymlを以下のように準備します。

serverless.yml
service: github-appsync
provider:
  name: aws
  runtime: nodejs8.10
functions:
  graphql:
    # this is formatted as <FILENAME>.<HANDLER>
    handler: index.graphqlHandler
    events:
    - http:
        path: graphql
        method: post
        cors: true

plugins:
  - serverless-webpack
  - serverless-offline

custom:
  webpack:
    webpackConfig: ./webpack.config.js
    includeModules: true

webpackでビルドするためにwebpack.config.jsも準備しました。

webpack.config.js
const slsw = require("serverless-webpack");
const nodeExternals = require("webpack-node-externals");

module.exports = {
  entry: slsw.lib.entries,
  target: "node",
  devtool: 'source-map',
  externals: [nodeExternals()],
  mode: slsw.lib.webpack.isLocal ? "development" : "production",
  optimization: {
    minimize: false
  },
  performance: {
    hints: false
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: "babel-loader",
        include: __dirname,
        exclude: /node_modules/
      }
    ]
  }
};

以上の環境で、以下コマンドによりAWS Lambdaにデプロイします。

sls deploy

オフラインで検証した結果、以下の通り2つのAPIがマージできていることがわかります。

image.png

まとめ

既存APIであるGithub APIと自作のAppSync APIを結合させました。
結果、Lambda上に新たなGraphQL APIを起動できました。
スキーマスティッチングを使うと、もとのAPIのスキーマの差分だけを書くことで結合ができました。
AppSyncやGithubの個々の認証情報もLambda上でまとめてくれるので、端末から入力不要になります。
AppSyncのAPIと外のAPIをさくっと結合した新たなBFFを作るときに、Lambdaは重宝しそうです。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2