『Production Ready GraphQL』の「Anemic GraphQL」

この記事は はてなエンジニア Advent Calendar 2022 の2023年1月3日の記事です。

昨日は id:onkストーリー性のあるプレゼン - id:onk のはてなブログ でした。


『Production Ready GraphQL』といえば Production Ready GraphQLはGraphQLを採用するなら必ず読んでおきたい良書 などでも紹介されていますが、GraphQL APIを設計・開発・利用するなら大変参考になる良書ですね。はてな社内でもGraphQL開発における前提本ですね、などと言われている模様です。

この中で好きな箇所の1つが「Specific or Generic」という節の「Anemic GraphQL」という項です。たくさん引用するわけにもいかないので冒頭だけ見ますと、

Anemic GraphQL is something I stole from the Anemic Domain Model, a pattern popularized by the great Martin Fowler. Anemic GraphQL means designing the schemas purely as dumb bags of data rather than designing them thinking of actions, use cases, or functionality. This is best expressed by an example:

type Discount {
  amount: Money!
}
type Product {
  price: Money!
  discounts: [Discount!]!
}

というスキーマ・型について、割引や税額を考慮した合計金額を表示することを考えるという例です。クライアントサイドで price から discounts の合計額を引いて totalPrice を算出することはできるが、Producttaxes が増えたりすると誤った計算になってしまうのでクライアントサイドの計算コードの修正が必要になる。クライアントサイドで合計金額を表示するというユースケースに応じて ProducttotalPrice フィールドを追加しようね、というような話。

type Product {
   price: Money!
   discounts: [Discount!]!
   taxes: Money!
   totalPrice: Money!
 }

こうすると Product のフィールドが増えたり合計金額の計算ロジックが変わっても、totalPrice フィールドを表示していれば良いままで変更に強くなる。

かのマーティン・ファウラー氏が命名したドメインモデル貧血症を引き合いに出していますが、GraphQLのスキーマ・型も単なるデータの入れ物に留まらず、ドメインユースケースに応じたフィールドを設計・公開するのが望ましいですねというお話でした。


メルペイのvvakameさんによる次の記事でも、同じような(と id:ikesyo が認識している)ことが言及されています。

engineering.mercari.com

Componentを構成するとき、1つのUI要素を表示するために複数のGraphQLのフィールドを組み合わせる必要がある場合、その処理をバックエンド側で肩代わりするcomputed fieldを導入することを検討してください。
たとえばメルカリの出品物に何個のコメントがついているかのバッジをUIに表示したいとします。しかしGraphQLスキーマにコメント数のフィールドが存在しない場合、クライアント側でコメントを全件取得し、その数をカウントするコードを書く必要があります。これは不便です。
クライアント側は不要なフィールドの取得をして、面倒なページングの処理も必要になります。バックエンド側もいくつかの不便を強いられます。個別のキャッシュが難しくなること、UIに対して過剰な本来必要な分以上のデータが取得されてしまうこと、これによりbackend主導の改善がより困難になることです。
バックエンド側はGraphQLのフィールドが要求されていることはわかっても、それがUI上でどのように使われているかは分析できません。どういったcomputed fieldが必要であるかは、基本的にはクライアント側のリクエストベースで検討されるべきです。
computed fieldを定義することをためらわないこと。どういうcomputed fieldを作るべきかは、バックエンドエンジニアが考えることは難しいでしょう。だから、クライアントエンジニアから相談があった場合、快く応じましょう。それが複雑な計算が要求されるとしても、必要ならばやるべきです。
なぜならば、バックエンド側が実装を拒んだ場合、単にクライアント側で同等の処理が実装されるだけだからです。しかも、それがどんなに重たい処理であろうとも、たった1つのUIを表示するために大量のリソースを消費するものだとしても、それを最適化する余地はあなたの手から離れてしまいます。computed fieldとして実装してあげれば、その処理が重かったらmemcachedに突っ込むなりなんなり、あなたが自由にチューニングすることができます。

こちらの記事もとても参考になる・自分達でも実践していることが多く、おすすめです。

あわせて読みたい

developer.hatenastaff.com