vue-apolloでdefaultClientを無理矢理リアクティブ(動的)に切り替える

# この記事は？
[DMM advent calendar 2020](https://qiita.com/advent-calendar/2020/dmm) 5日目(3日遅れ)の記事です。遅れてごめんなさい・・・

なぜ意地でも5日目にしたかったかというと、12/5が誕生日だったからです🎉

# 概要
vue-apolloにはmultiple clientと呼ばれる機構が存在する。

例えば、GraphQLサーバーが2つに別れており、ユーザー系の情報はAに、商品系の情報はBに格納されているといった場合に、エンドポイントや認証情報を各クエリ毎に切り替えるといった用途に使える。

しかし、今回やりたいのは、同じクエリに対して認証状態に応じてエンドポイント≒クライアントを切り替えたいのである。

その手法として、認証状態を管理しているVuexStore上でvue-apolloのprovider情報を書き換える事で動的にクライアントを切り替える方法を探していた。

ここに記載している方法を使えば、例に上げているエンドポイントだけでなくトークンや認証情報等の定義可能な情報をまるごと切り替えできる。

結論が先に見たい方はこちら → [まとめ](#まとめ)

# 切り替える手段を探る

## vue-apolloのSmart Apolloで切り替えできないか

まず、nuxt.config.js内でこんな感じでエンドポイントだけ切り替えたいが、それ以外は共通みたいな感じで定義しておく

```js:nuxt.config.js
{

/* ~以上略= */

  apollo: {
    clientConfigs: {
      default: {
        httpEndpoint: 'https://example.com/guestGateway'
      },
      user: {
        httpEndpoint: 'https://example.com/gateway'
      }
    }
  },

/* ~以下略~ */

}
```
で、このclient設定を元にSmartQueryでclientを分ける場合、 `guest` のクライアント設定を使用するコンポーネント上でこのように書くと思う

```Vue:viewer.vue
viewer: {
  query:gql`
    query { 
      viewer { 
        login
      }
    }
  `,
  client: 'user'
}    
```

が、この辺りのドキュメントがびっくりするほど見当たらない。何ならAPI ReferenceのsmartQueryの項にmultiple Clientに関するキーが全く書かれていない。Advanced TopicのMultiple Clientの項目に書かれてる内容がドキュメントの全て。

https://apollo.vuejs.org/guide/multiple-clients.html

~~Vue界隈のこういう応用ドキュメントが雑なとこが嫌いなんだ~~ そんなわけで、ここにコードを足して、data属性内の情報や算出プロパティの情報を元に `default` と `guest` を入れ替えてみる。


```Vue:viewer.vue
viewer: {
  query:gql`
    query { 
      viewer { 
        login
      }
    }
  `,
  client: this.isLogin ? 'user' : 'default' // ここでエラー
}    
```

さて、このキーは果たしてリアクティブに書けるのか、答えはノーである。

![スクリーンショット 2020-12-08 1.02.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104876/a960db30-361e-fae8-9c68-879c7bed64cf.png)


VScodeのVutrが見事にエラーにするし、実行しても、もちろんErrorになる。

今回やりたいのはログイン状態に応じてエンドポイントを切り替えるという動作。既にこの時点で出来ない。

もちろん、算出プロパティ等で書くと、多分created前に呼ばれるのでundefinedになって動かない。

## Apollo Providerにインジェクションしてみる

じゃあこれをどうするか。

vue-apolloには3種類ほどGraphQLへアクセスする手段を提供している。

最初に述べたdata属性のようにapollo自信が振る舞う「SmartQuery」、

v-slotを使ってクエリの結果をインジェクションする「Apollo components」、

そして、もっともオーソドックスな、react向けに使われるApollo clientと同じような使い方ができる「Apollo Provider」「Dollar Apollo」がある。

※vue-apollo v4からはVue3のCompositionAPIに対応した apollo-composable が使用できますが、この話はVue2×vue-apollo v3での悩みを解決したときの話なので割愛します。

「Dollar Apollo」のプロパティには `provider` と呼ばれるApollo Providerが挿入されたキーを持っている。

https://vue-apollo.netlify.app/api/dollar-apollo.html

そして、そのApollo Providerはnuxt.config.jsのapolloオプションの中身と同じく、 `defaultClient ` を持っている。

https://vue-apollo.netlify.app/api/apollo-provider.html

では、このApollo Providerが持つ `defaultClient` を上書きすればどうなるか。

```Vue:hoge.vue
this.$apollo.provider.defaultClient = this.$apollo.provider.clients.user;
```

なんと、これでdefaultClient設定を上書き出来てしまう。この処理を任意の処理で書けば、apolloで使用する全ての処理を異なるclientで実行可能になる。

また、こうすればdefaultClientsに戻すことも可能

```Vue:hoge.vue
this.$apollo.provider.defaultClient = this.$apollo.provider.clients.defaultClient;
```

# まとめ

## 実装方法

nuxt.config.jsにapolloのオプションにclientconfigを設定して複数のclientsを定義しておく。

```js:nuxt.config.js
{

/* ~以上略= */

  apollo: {
    clientConfigs: {
      default: {
        httpEndpoint: 'https://example.com/guestGateway'
      },
      user: {
        httpEndpoint: 'https://example.com/gateway'
      }
    }
  },

/* ~以下略~ */

}
```

そして、任意のmethodやvuex storeのaction等で下記処理を定義する。

```Vue:hoge.vue
// user client を使う場合
this.$apollo.provider.defaultClient = this.$apollo.provider.clients.user;

// 元のdefault client を使う場合
this.$apollo.provider.defaultClient = this.$apollo.provider.clients.defaultClient;
```

## 完走した感想

- ログインメンバーかゲストかでエンドポイント違うのもどうなんだろうとは思うけど、トークンが異なるとかはよくある話なので、この手法は使いみちありそう
- てかこんな設定の仕方でええんかな
- 公式ドキュメントもっとちゃんと書いてくれ〜

VueとかNuxtをただ単に使うみたいな記事はやたら多いけど、こういうvue-apolloを使うとかちょっと込み入ったライブラリを使うみたいな応用的な話はなかなか日本語記事に上がってこないので、役に立てたら幸いです。

## 明日の投稿は〜？

6日目は [@naka_kyon](@naka_kyon) さんです。

ちょうどフロントの話題から比較的バックエンドサイドなGraphQLのお話です。この記事の問題にぶち当たったときに考えた、このGraphQL本当に要るの？みたいなお話とか、雑にやるとあるあるになっちゃうよねーみたいなあるあるネタ満載で面白いのでぜひご覧ください。

[GraphQL あるある(ないない含む) 15選](https://qiita.com/naka_kyon/items/eca9b9d0d369dcdb3f3d)