ApolloServerを使ってGraphQLのSubscriptionを調査する

#はじめに
GraphQLの概要の紹介、QueryとMutationの動きを確認した[前回](https://qiita.com/shotashimura/items/3f9e04b93e79592030a4)の続きです。
本記事はGraphQLのSubscriptionについての投稿となります。

Subscription(サブスクリプション)とは直訳で「購読」という意味で、月額課金サービスなどの総称で耳馴染みのある言葉ですが、
GraphQLにおいては、Mutationにおいてデータ操作をした際に、クライアント側でサーバーから発行された変更情報を受け取ることを指し、チャットアプリや通知機能、ログイン認証などでの利用が考えられます。
[こちら](https://qiita.com/yoshii0110/items/fbee34d5c235a2a064f9#%E3%82%B5%E3%83%96%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%9F%E3%83%A6%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%82%B9)の解説が大変分かりやすいので、オススメです。

今回は前回サーバーを立てたExpress-GraphQLに代わりApolloを利用して、GraphQLサーバーを立てた上でSubscriptionの動きを確認していきたいと思います。

##Apolloとは

>Apolloは、データグラフを構築するためのプラットフォームであり、アプリケーションクライアント（ReactやiOSアプリなど）をバックエンドサービスにシームレスに接続する通信レイヤーです。

クライアントとサーバーの中間にあるApolloサーバーによって、GraphQLによるデータのやり取りがスムーズになります。

**アーキテクチャ例**
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357734/82d9651f-f959-3ae5-fffc-393b5c71bd64.png)

[公式ドキュメント](https://www.apollographql.com/docs/)

##下準備
プロジェクトを作った後に、npm経由でapollo-serverをインストールします。

```
mkdir graphql-server
cd graphql-server
mkdir resolver
touch index.js db.js resolver/{Query.js,Mutation.js,Subscription.js}
npm init
npm install apollo-server apollo-server-express --save
```

##サンプルコードを読みやすくするために

###PubSub

PubSubはSubscriptionによる通知イベントを作成する工場のような存在。

```example.js
const { PubSub } = require('apollo-server');

const pubsub = new PubSub();
```

###gql

gqlというタグ付きテンプレートリテラルと呼ばれる特別な関数を使用して、GraphQLに関連する定義をJavaScripの文字列として表現できるようにします。

```example.js
const typeDefs = gql`
  type Subscription {
    postAdded: Post
  }

  type Query {
    posts: [Post]
  }

  type Mutation {
    addPost(author: String, comment: String): Post
  }

  type Post {
    author: String
    comment: String
  }
`
```


###Resolver引数
リゾルバ関数には固定の役割を持つ４つの引数があります。

| 引数（昇順） | 	説明 |
|:-----------|:------------|
|parent    | 親リゾルバからの戻り値。       |
|args   | フィールドに提供されたすべてのGraphQL引数を含むオブジェクト    |
|context       | 特定の操作に対して実行されるすべてのリゾルバ間で共有されます。これを使用して、認証情報やデータソースへのアクセスなどの操作ごとの状態を共有します。      |
|info         | フィールド名、ルートからフィールドへのパスなど、操作の実行状態に関する情報          | 


##コードの紹介と解説
作成したファイルにコードを書いていきます。
QueryとMutationについての概要は省略致しますので、はじめてGraphQLに触られる方は[こちら](https://qiita.com/shotashimura/items/3f9e04b93e79592030a4)の記事からどうぞ！😊


先ず、データベース代わりのJavaScriptファイルを用意します。

```db.js
const posts = [{
    id: '1',
    title: 'こころ',
    author: '夏目漱石'
}, {
    id: '2',
    title: '舞姫',
    author: '森鴎外'
}, {
    id: '3',
    title: '羅生門',
    author: '芥川龍之介'
}]

const db = {
    posts,
}

module.exports  = db;
```

続いてQuery,Mutation,そして今回のテーマであるSubscriptionのリゾルバ関数を書いたファイルを順番に用意していきます。


```Query.js
const Query = {
    posts(parent, args, { db }, info) {
       //クエリを書いた時に引数が「ない」時
       //模擬データベースの内容を全て表示
        if (!args.query) {
            return db.posts
       //クエリを書いた時に引数が「ある」時
       //引数とtitle or authorが一致したものだけを表示
        }else{
            return db.posts.filter((post) => {
            const isTitleMatch = post.title.toLowerCase().includes(args.query.toLowerCase())
            const isAuthorMatch = post.author.toLowerCase().includes(args.query.toLowerCase())
            return isTitleMatch || isAuthorMatch
        })
    }
    }
}

module.exports  = Query
```
クエリのリゾルバ関数です。引数の有無で条件分岐をしています。
JavaScriptで条件を細かく書くことで、**「本当に必要な情報だけを取得できること」がGraphQLの強みです。**

```Mutation.js
const Mutation = {
    createPost(parent, args, { db, pubsub }, info) {
        const postNumTotal = String(db.posts.length + 1)
        const post = {
            id: postNumTotal,
            ...args.data
        }

        //データベース更新
        db.posts.push(post)
        //Subscription着火
        pubsub.publish('post', { 
                post: {
                    mutation: 'CREATED',
                    data: post
                }
             })
        return post
    },
    updatePost(parent, args, { db, pubsub }, info) {
        const { id, data } = args
        const post = db.posts.find((post) => post.id === id)
        if (!post) {
            throw new Error('Post not found')
        }

        if (typeof data.title === 'string' && typeof data.author === 'string') {
            //データベース更新
            post.title = data.title
            post.author = data.author
            console.log(post)
            //Subscription着火
            pubsub.publish('post', {
            post: {
                mutation: 'UPDATED',
                data: post
            }
        })
        }

        return post
    },
    deletePost(parent, args, { db, pubsub }, info) {
        const post = db.posts.find((post) => post.id === args.id)
        const postIndex = db.posts.findIndex((post) => post.id === args.id)

        if (postIndex === -1) {
            throw new Error('Post not found')
        }
        //データベース更新
        db.posts.splice(postIndex, 1)
        //Subscription着火
        pubsub.publish('post', {
                post: {
                    mutation: 'DELETED',
                    data: post
                }
            })
        return post
    },
}

module.exports  = Mutation
```
Mutationのリゾルバ関数では、データベースの更新とSubscriptionの着火をしています。

```Subscription.js
const Subscription = {
    post: {
        subscribe(parent, args, { pubsub }, info) {
            return pubsub.asyncIterator('post')
        }
    }
}

module.exports = Subscription
```
Subscriptionのリゾルバ関数です。
**pubsub.asyncIterator**でサブスクリプションのイベントを非同期でリッスンします。

説明の関係で最後になりましたが、
スキーマの定義とサーバー起動のファイルになります。

```index.js
const  {ApolloServer,PubSub,gql} = require('apollo-server');
const db = require('./db')
const Query = require('./resolver/Query')
const Mutation = require('./resolver/Mutation')
const Subscription = require('./resolver/Subscription')

//スキーマ定義
const typeDefs = gql`
type Query {
  posts(query: String): [Post!]!
}

type Mutation {
  createPost(data: CreatePostInput!): Post!
  deletePost(id: ID!): Post!
  updatePost(id: ID!, data: UpdatePostInput!): Post!
}

# Subscription
type Subscription {
  post: PostSubscriptionPayload!
}

input CreatePostInput {
  title: String!
  author: String!
}

input UpdatePostInput {
  title: String
  author: String!
}

type Post {
  id: ID!
  title: String!
  author: String!
}

######################
# Subscriptionで利用
######################

enum MutationType {
  CREATED
  UPDATED
  DELETED
}

# Subscriptionのフィールド
type PostSubscriptionPayload {
  mutation: MutationType!
  data: Post!
}

`
//PubSubのインスタンスを作成,Subscriptionが利用可能に！
const pubsub = new PubSub()

const server = new ApolloServer({
    typeDefs: typeDefs,
    resolvers: {
        Query,
        Mutation,
        Subscription,
    },
    context: {
        db,
        pubsub
    }
})

server.listen().then(({ url, subscriptionsUrl }) => {
    console.log(`🚀 Server ready at ${url}`);
    console.log(`🚀 Subscriptions ready at ${subscriptionsUrl}`);
  });
```
サーバーを立てる際にスキーマやリゾルバ、PubSubなどを引数に指定しています。
指定した引数はQueryやMutationそしてSubscription、それぞれの処理で利用をしています。


準備が出来たらターミナルから起動させます。

```
node index.js
🚀 Server ready at http://localhost:4000/
🚀 Subscriptions ready at ws://localhost:4000/graphql
```
前回同様にGraphQL IDEにてテストをしてみます。

Subscriptionを利用するには、クエリを書いた後にYouTubeの再生ボタンのような矢印をクリックします。
写真のように赤いポーズボタンに変化したら、Subscriptionの起動成功です。

![スクリーンショット 2020-10-15 23.00.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357734/9074c7ca-c114-e52a-df94-25b3d81290c0.png)

先ずはMutationで新しくデータを作ってみます。
![スクリーンショット 2020-10-15 23.00.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357734/afae0f8a-9f1d-0a9b-8079-37295acfd274.png)

そのままSubscriptionのタブに移動して、結果を確認します。
![スクリーンショット 2020-10-15 23.01.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357734/b2b8f9df-bc79-bcc5-c46a-2bd64423e75a.png)
Apolloサーバーからデータが新しく作成されたことに対する変更情報を受け取ることが出来ました。


データの更新、そして削除についても確認します。

Mutationによるデータの更新。
![スクリーンショット 2020-10-15 23.01.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357734/f01f0b6f-4521-a826-ad49-87c020844f4a.png)

Subscriptionにてデータの更新の確認。
![スクリーンショット 2020-10-15 23.01.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357734/7d11d782-6f0e-3f4f-2d5d-71849c399cd0.png)

Mutationによるデータの削除。
![スクリーンショット 2020-10-15 23.02.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357734/fbb31130-2afa-83b2-0f59-e526330fd2a6.png)

Subscriptionにてデータの削除の確認（更新と削除を連続して行っています）

![スクリーンショット 2020-10-15 23.02.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357734/890f53d1-e5c1-9a91-7569-f9fa157728b4.png)

以上、Mutationによるデータの更新をSubscriptionを通して確認することが出来ました！！🚀🚀

###おわりに
QueryやMutationと比較してSubscriptionは理解に時間がかかったものの、
実際にコードを書き、GraphQL IDEでテストをすることによって自分の中で腹落ちさせることが出来ました。
[次回の記事](https://qiita.com/shotashimura/items/7c2a4a656ea1239ec190)ではフロントにVueを使った上で、実際のアプリケーションの中でGraphQLを使っていきます！

それでは、また！😊