GraphQLはCleanArchitectureとの相性が抜群に良い


昔はAPI開発といえばREST一択でした。
しかし、GraphQLができて、GitHubもGraphQLが採用されたことから、API開発にはRESTとGraphQLの2通りの方法で開発ができるようになりました。
実際に私もAPI開発にGraphQLを使っています。
その経験からも、GraphQLのAPI開発にはクリーンアーキテクチャがとても相性が良いと感じています。
今回は、GraphQLとクリーンアーキテクチャについて書いていこうと思います。

GraphQLとは

まずGraphQLとは何かということを簡単に説明しておきます。
GraphQLはAPIを叩く時のリクエストの方法のになります。RESTもAPIを叩く時のリクエストの方法ですが、何が違うのかというと柔軟にレスポンスを設定することができるという点が大きく違います。
例えばユーザー情報を取得するAPIを叩く場合を考えてみます。

RESTの場合

RESTの場合だと、https://localhost/user/1のようなエンドポイントを叩くと以下のようなレスポンスが取れるとします。

{
	"user_id": 1,
	"user_name": "山田太郎",
	"email": "[email protected]",
	"tasks": [
		{
			"task_id": 1,
			"task_title": "要件定義"
		},
		{
			"task_id": 2,
			"task_title": "設計書作成"
		}
	]
}

このレスポンスはガッチリと固まっているので、「tasksの情報は要らないんだけどなぁ...」ってときも問答無用でtasksの情報までしっかりと取れてしまいます。
なので、その場合はtasksの情報を除いたAPIを新しく作る必要があります。

GraphQLの場合

対してGraphQLの場合は、RESTと同じように

{
	"user_id": 1,
	"user_name": "山田太郎",
	"email": "[email protected]",
	"tasks": [
		{
			"task_id": 1,
			"task_title": "要件定義"
		},
		{
			"task_id": 2,
			"task_title": "設計書作成"
		}
	]
}

のようにレスポンスを受けることができます。
そしてRESTの場合で「tasksの情報は要らないんだけどなぁ...」ってなった問題、GraphQLだと同じエンドポイントで

{
	"user_id": 1,
	"user_name": "山田太郎",
	"email": "[email protected]",
}

のようにtasksの情報を除いてレスポンスを受け取ることができるので、新しくAPIを作る必要もありません。
GraphQLはクエリ言語とも呼ばれているので、サーバー側に欲しいレスポンスをクエリとして投げるようなイメージになります。
身近な例で言えば、DBにクエリを投げて欲しいデータを取れるイメージに近いかと思います。

CleanArchitectureとは

次にCleanArchitectueについて簡単に説明します。
CleanArchitectureと言えば必ず出てくるのがこちらの画像。

これは、システムを4つのレイヤーに分けることで、保守性、拡張性を高めたシステムを作れるというものです。
(参考:https://blog.tai2.net/the_clean_architecture.html)

ではなぜ私が、GraphQLを使う場合はCleanArchitectureを採用した方が良いと言っているのか説明していきます。

作るもの

今回の説明用に作るAPIはユーザーIDを指定してユーザー情報とタスクの一覧を取得できるものになります。
ER図は下図の通りです。

レスポンスは下記のようになる想定です。

{
	"user_id": 1,
	"user_name": "山田太郎",
	"tasks": [
		{
			"task_id": 1,
			"title": "要件定義",
			"status": "未着手"
		}
	]
}

クリーンアーキテクチャのディレクトリ構造

上で説明したクリーンアーキテクチャの図に合わせて次のようなディレクトリにします。

app/
  ├ controllers
  │  └ graphql_controller.py
  └ presenters
  │  └ user_presenter.py
  ├ usecases
  │  └ get_user.py
  ├ entities
  │  └ user_entities.py
  └ main.py

それぞれのディレクトリの役割は下記の通り。

controllers

リクエストを受け取って、下位のレイヤーで処理できる形にするところ。
GraphQLはidをBase64の形でリクエストを送ることが推奨されています。
そのため、Base64を処理できる形にする必要があります。
今回は、user_idをVXNlcjox1に変換するのがcontrollerの役割になります。

usecases

このアプリのメイン処理。今回はuserの情報を取得する処理を実行するところ。
今回はuser_idをフィルターにしてDBからレコードを取得する役割になります。

presenters

レスポンスの形を作るところ。
今回は下記のスキーマの形にします。GraphQLはidをBase64の形にして、一意にする必要があります。idはDBからデータを取得したときはint型なので、Base64にエンコードする必要があります。

@strawberry.experimental.pydantic.type(model=TaskBaseModel)
class TaskType:
    id: ID
    title: str
    status: str
    user_id: int


@strawberry.experimental.pydantic.type(model=UserBaseModel)
class UserType:
    id: ID
    user_name: auto
    email: auto
    tasks: auto

entitites

各レイヤー間のデータを管理するところ。例えば、controller層から、usecaseにデータを渡すときは下記の型にして渡します。こうしておくと、usecaseではUserFilterの形でデータを渡してもらえれば処理できることになります。

class UserFilter(BaseModel):
    user_id: int

処理の流れ

クリーンアーキテクチャーの図の通りに処理を進めます。具体的には、
リクエストを受け取る→controllerでusecaseに渡せる形に変換する→usecaseでDBからレコードを取得する→presenterでスキーマを作る→レスポンスを返す

仕様変更

ここまでで、仕様を満たすAPIは作成できました。
そしてここから、GraphQLで実装する場合はなぜクリーンアーキテクチャを採用した方がいいのかという本題のところを説明します。

RESTで作って欲しい

まさかの、仕様変更...w
GraphQLで作ってきたのに、やっぱりGraphQLじゃなくてRESTで作ってほしいとのこと。
この仕様変更はかなり影響範囲が大きくなる感じがします。。。

今回はGraphQLで作っていたことで

ですが、今回はクリーンアーキテクチャで作っています。GraphQLとRESTの違いはリクエストの受け方とレスポンスの返し方です。
とすると、影響範囲はcontrollerとpresenterのところだけになります。
影響範囲が明確になるだけでもかなりのメリットはあると思います。

controllerの修正

まずリクエストを受け取った後の処理である、controllerの修正からしていきます。ですが、graphql_controller.pyを修正することはしません。新たにrest_controller.pyとしてREST用のコントローラーを作ります。
ポイントはuserFilterの形にしてusecaseの処理に渡すことです。

presenterの修正

次にレスポンスを返す処理であるpresenterの修正になります。ですが、FastAPIの機能を使うとresponse_model=UserBaseModelのところでレスポンスを作られて、UserBaseModelの型はusecaseで返される型であるので修正はありません。

@app.get("/user/", response_model=UserBaseModel)
def get_user(user_id: int):
    return RestUserController().get_user(user_id)

修正結果

クリーンアーキテクチャで作っていた結果、GraphQLからRESTに変更した場合修正を入れたのはcontrollersとmain.pyだけで済みました。そして、GraphQLで作った時の処理には一切手を触れていません。
これの何がすごいかというと、もし「やっぱりGraphQLにしてほしい」と言われてもすでに完成しているものがあるので一瞬で変更ができることです。

app/
  ├ controllers
  │  └ graphql_controller.py
  │  └ rest_controller.py → REST用のコントローラー追加
  └ presenters
  │  └ user_presenter.py
  ├ usecases
  │  └ get_user.py
  ├ entities
  │  └ user_entities.py
  └ main.py → RESTのエンドポイントと追加

最終的なコードは下記に置いています。
https://github.com/y-p-e/graphql_fastapi_clean_architecture

まとめ

GraphQLとクリーンアーキテクチャはとても相性が良いと思っています。特に、GraphQLを初めて導入する場合など学習コストや有効な使用方法が分からなかったりで使い慣れているRESTへ変更する場合も出てくると思っています。実際、そのような記事を見かけたこともあります。
そんな場合でも、クリーンアーキテクチャで作っておけば核の機能であるusecaseの部分はそのまま使えて、リクエストとレスポンスを処理するcontrollerとpresenterだけを変更すれば良いというのは非常に強力だと思います。
また、GraphQLの時だけに限らず、クリーンアーキテクチャの考え方はアプリの保守性、拡張性を高めるためにも有効なものが多いので積極的に採用して良いのではと思います。