プロダクションレベルのGraphQL実装をしてみて印象的だった二つのこと
はじめに
Ubieでエンジニアをしているおおいしつかさです(たまにしかブログを書かないので、毎回自己紹介しておきます)。
Ubieのとある機能を独立したサービスとして切り出すことになって、フロントエンドとそのサービス間のプロトコルにGraphQLを使うことになりました。
APIの開発というと、今までのぼくの経験ではそのほぼすべてがREST APIになります(固定長のバイナリボディを持つガラケー用アプリの謎のAPIとかもやったことあります)。
GraphQLでの開発経験がない中で、いろいろと学ぶところも多かったです。UbieにはGraphQLに精通したエンジニアが何人もいるのですが、その内のひとりと一緒に仕事ができたのでいろいろと教えてもらいました(知りたいことがあったら社内にだいたいスペシャリストがいるのがUbieのいいところのひとつ)。
GraphQLについての記事はたくさんあると思うので、細かな技術的な話はそういう優秀な記事にお任せしましょう。
ここではプロダクションレベルのGraphQL実装に初めて携わったエンジニアが印象的に感じた部分について記していこうかと思います。
クライアントファースト
まずはGraphQLのスキーマ設計から入ったのですが、その最中はもちろん、実装に入ってからも「お、ここは今までのAPI設計ではそんなに強く意識してなかったぞう」ということがあります。
それは、GraphQLがクライアントファーストなAPIだということです。
もちろん、それまで開発してきたREST APIもクライアントのためのものであることに変わりはありませんでした。
クライアントのニーズに合わせてクエリパラメータで取得できるデータ量をコントロールしたり、ある特定のクライアントのためだけに特殊なパラメータを用意したり、RESTの考えに無理矢理当てはめてリソースとは思えないものもリソースとして扱ったりしてきたことがみなさんにもあると思います。
一方、サービスのAPIはそのビジネスドメインを表現しているものだという意識もあります。
なので、「とにかくデータだけ渡してほしい。ロジックはクライアントサイドで組み立てる」という要求には応じられないことも確かです(サーバーサイドとクライアントサイドでビジネスロジックの二重管理をしたいなら話は別ですが)。
ここでバックエンドエンジニア寄りの意識が強くなりすぎると、以下のような考え方に行き着く人も出てきてしまうと思います。
ビジネスドメインというのは普遍的であり、それに則っていればAPIも自ずと形が決まってくるし、それはクライアントには左右されない
こうなるとクライアントのニーズはあまり考えないようになってしまうので注意が必要です。
ビジネスドメインが普遍的であるという部分については重要です。それを軸として適切に設計すればクライアントでも扱いやすいものになるでしょう。
しかし、あまりにもクライアントのことを無視しすぎると、APIのインターフェースが独りよがりになり、クライアントからするといまひとつ使いにくいものになってしまいます。
GraphQLはクライアントファーストのAPIです。クライアントのユースケースを念頭においてスキーマを設計します(前提としてビジネスドメインが適切に設計されていることは当然必要です)。
…という意識が頭の中にあるだけで、設計するときも実装中に迷ったときも判断の軸にすることができました。
GraphQLの実装に携わってみて、一番印象的だった部分はここだったりします。
クライアントを第一に考えるといいところは他にもあって、それはバックエンドの内部実装がにじみ出てきてしまう危険性を下げることができるからです。
バックエンドのDBのテーブル構造がそのままインターフェースに現れるなどは最悪だと思いますが、クライアントを第一に考えることでそういうことを避けることができます。
コンポーネントが必要な情報はコンポーネントだけでコントロールできる
フロントエンドはReactで実装していたのですが、コンポーネント内で必要なデータは、当初ページのトップなどで取得してからPropsでコンポーネントに渡そうとしていました。
こうなるとコンポーネントの仕様が変わって必要なデータが変化したときに、コンポーネントの利用元である親のコンポーネントでクエリを書き直す必要があります。もちろん、コンポーネント側も利用するデータの変化に対応するため、二箇所以上の修正が必要になってきます。
これを解決するために、GraphQLのFragmentという仕組みを利用することができます。
GraphQLのFragmentとは、クエリの一部を定義して他の場所で使うことができる機能です。
子コンポーネント側
fragment UserBox_Person on Person {
name
age
sex
level
}
親コンポーネント側
query {
Persons {
id
name
...UserBox_Person
}
}
(あー、コードを出さないことをこの記事を書く前に誓ったのですが、誓いは破られるものです)
子コンポーネント側で定義したFragmentを親コンポーネントのクエリで展開しています。
これによって、子コンポーネント側でPersonのaddressがほしくなったとしても、親コンポーネントは一切触らずに必要なデータを取得することができるようになります。
これは賢いですよね? 最初にこれを知ったときは感動しました。そして10年くらい前に仲間と議論していたことを思い出しました。
Webページのコンポーネントごとに独立してAPI(当時なので当然RESTです)をコールすれば、変更も機能追加もコンポーネント内に閉じることができる。凝集度が高まって結合は疎になるしいいことだらけだ! コンポーネント指向設計だ! なんて盛り上がったりしたものです。
結局いろいろあってそれを実現することはなかったのですが、ここにそれが形となって存在しているじゃないですか。
これだけでGraphQLが好きになってしまいました。
おわりに
既存サービスで複数の責務が存在していた部分を独立したサービスとして切り出すという仕事をした中で、はじめて本格的に触ったGraphQLについて感じたことを書いてみました。
ぼくと同じくRESTおじさんのエンジニアの参考にしていただけると幸いです。