API呼び出しの為に、API Aggregationしたいんじゃ！(NestJS ✕ GraphQL)

## さあ、ここで、問題です！

貴方は下図の3つのAPIを持っています。
電子メールでの新規会員登録機能を作る場合、どういった処理を書きますか？
シンキングタイムは10秒です。

チッチッチッチッ (時計の音)

![aaaa.drawio (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170923/31224b05-c6b7-c668-8c8d-c36243724495.png)

はい、考えてくださって、ありがとうございます。
答えは用意しておりません。なぜなら1つだけの最適解は無いからです。

本記事の目的はたった一つの最適解が無い中で、自分ならこう考えるをシェアさせてもらいたいです。

## API呼び出しのよくある要件

今回の要件は以下だと思います。

1. ユーザデータを作成したい
2. 新規作成と同時に何かしらでログインしてもらえるようトークンを発行したい
3. よくある「ご登録ありがとうございます。」メールをエンドユーザに送りたい

![aaaa.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170923/a297289b-aeb6-74cf-f919-707134bd0fdd.png)

## よくあるパターンその①: 必要な順番ごとにコールするメソッドを作成する

``` plaintext:こんなプログラム言語ないよ
func registerMember {
    client.post("/users", {nickname: "田中"})
    client.post("/auth", {id: "abc@yahooooo.co.jp", pw: "1234567890"})
    client.post("/email", {mailAddress: "abc@yahooooo.co.jp"})
}
```

![aaaa.drawio (9).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170923/ae282b5b-73b8-9ba3-14a0-5dade74cc6db.png)


## よくあるパターンその②: そもそもuser APIに全部まとめちゃう
「認証トークン作成もメール送信もユーザAPIの責任だよね。」な考え方です。
※ 「サイドカー」と書いていますが、特に気にしないでください。

![aaaa.drawio (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170923/c10b1d52-faee-6da7-929f-3b652b7b978e.png)


## それぞれのアプローチに対する課題感

### よくあるパターンその①の問題

- ロジックの掛け合わせパターン分だけエンドポイントが作成されてしまう
- エンドポイントを別途ドキュメント管理する必要が出てくる
- 全く同じAPIの呼び出しを何度もいろんな箇所に書く必要が出てくる

### よくあるパターンその②の問題

- user APIの本来果たすべき「ユーザリソースの作成」以外のことも行っているため、つなぎ込み先のAPIがemailからslackに変わった途端に複雑性が一気に増す
- つなぎ込み先との関連性を考慮する必要があるため、user APIのインターフェースが複雑になる
- auth APIの修正をしただけなのにuser APIの修正もしなければいけなくなる

## 本記事で紹介したい呼び出しパターン

**NestJSとGraphQLを使用して、Aggregation APIを作成する。**

![aaaa.drawio (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170923/be9e376b-f128-ecdc-4a7b-7ec66023eaf1.png)


GraphQLをまだ触ったことが無い方向けに説明すると、GraphQLは以下のような制約があります。

- エンドポイントは「/graphql」のたった一つのみ
- リソースの操作はquery(パラメータ)で行う
- ドキュメンテーションの作成を行わなくてもコードから自動的にインターフェース(query)定義書を生成してくれる
- 厳格なスキーマ設定があるので、存在しないパラーメータや異なるデータ型は許容されない

### つまり .... ! Queryを組み立てるだけで、一気に複数のAPIをコールできる !!

```graphql
mutation RegisterMember($email: String!) {
  ## ユーザ作成
  createUser(
    createUserInput: {
      nickname: "田中"
    }
  ) {
    nickname
  }
  ## 認証データ作成かつトークン返却
  createAuth(
    createAuthInput: {
      id: $email
      pw: "password"
    }
  ) {
    token
  }
  ## メール送信
  sendEmail(
    sendEmailInput: {
      subject: "hello"
      email: $email
    }
  )
}
```

### 銀の弾丸で無い点

- user APIのレスポンスデータを後続のauth APIに引き継げない
- 単一のエンドポイントしか提供しない為、場合によってエラーハンドルが複雑になる
    - statusは200しか返せません

user APIのレスポンスデータを後続のauth APIに引き継げない為、APIを3回もコールしなければいけないのか？
というとそういうわけではありません。

レスポンスパラメータをGraphQL上の変数として受け取れないだけで、内部的に異なるメソッド間でデータの引き継ぎは可能となります。

**そう、NestJSを使えばね。**
※ NestJS以外でも可能です。Railsのscaffoldのようにすべて勝手にお膳立てしてくれる点がメリットというだけです。


## NestJSのModuleについて
![aaaa.drawio (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170923/b0eb4596-3d9a-64e6-7b9d-7d42f54a0834.png)


NestJSは1つのModuleに対して、ResolverとServiceを紐付けることが可能となります。

ResolverはMVCフレームワークでいう、Controllerにあたります。
ServiceはMVCでいう、Modelにあたります。

本記事はAPI呼び出しの話なので厳密にリソースはNestJSが持たないのですが、(外部の)リソースの操作は行うため、Service(赤枠)でAPI Callします。

### Module間は参照可能

下図のとおりとなります。
その為、**mail Resolverがuser Serviceのメソッドを呼び出したりプロパティにアクセスすることが可能**となります。

![aaaa.drawio (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170923/553311ed-fbaf-4443-37b2-42cfa167b5dd.png)

### NestJS ✕ GraphQLが銀の弾丸で無い点②

- Module間の参照が増えれば増えるほど、各Service(赤枠)の変更が行いにくくなる (アプリケーションが複雑になる)
- 人によってはAPIに書くべきビジネスロジックをResolverやServiceに書き込んでしまう。
    - コード規約で回避可能ですが、意外と100%浸透させるのは難しいです。 


## 本記事まとめ
- それでもやっぱり自分はAPIを呼び出すなら、API Aggregationしたいんじゃ！
- 明確な最適解は無い。というか教えて下さい。お願いします。(懇願)
- 個人的にはBFFはブラウザから呼べた方がいいよねと思っております。
なので、email送信用のGraphQL関数を作成する際はアドレスと内容を受け取ったら宛先に送れるような関数はセキュリティ的にNGです。 (迷惑メール送り放題なので)
- テレビ千鳥が好きです。

## 採用PR

https://hrmos.co/pages/vivion/jobs/0000226

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307359/2000f749-3e82-7a47-b1c4-4444e237f79c.png" width=45%>