はじめに
ブログを更新するAPIを想像してください。
あなたは、ブログの記事タイトルだけを更新したいとします。
でもAPIの仕様によっては、タイトルだけじゃなく本文とかも全部送らないといけないことがありえます。
「変えたいのはタイトルだけなのに他のフィールドも送らなきゃなのはナンセンス」って考えたりしませんか?
また他にも新しいフィールドが追加されたときに生じる問題もあります。
たとえば、予約投稿のためにscheduledPublishDateというフィールドを追加したとします。
古いクライアントはその存在を知らないので送ってきません。
でもサーバーが「フィールドが送られてこなかった=nullで更新」と解釈すると、
意図せずscheduledPublishDateの値が消えるという事故が起きます。
これを防ぐには、「送られてきたフィールドだけを更新する」ための部分更新の仕組みが必要です。
つまり、RESTでいうPATCH相当のことをGraphQLでもやりたい、という話です。
なお、動くサンプルコードも用意しています。
Spring for GraphQLでどうやるのか
GraphQLでは、更新対象外のフィールドをundefinedで表現する事が可能です。
ここで重要なのは、サーバー側がundefinedを識別して、「undefinedのフィールドは変更しない」ように実装することです。
ArgumentValue でnullとundefinedを区別する
Spring GraphQLのArgumentValue<T>を使うことで実現できます。
このクラスを使うことで以下の3つの状態を区別可能になります。
| 状態 | 説明 | 処理 |
|---|---|---|
| undefined | リクエストに含まれていない | 更新対象外 |
| null | 明示的に送られた | 値を削除 |
| 値 | リクエストに含まれている | 値を更新 |
このように分けて扱えるようにすることで、更新・削除・対象顔のフィールドを明示的に区別できます。
Inputの定義
記事編集用のInputはこのようになります。
data class EditArticleInput(
@field:NotNull
val articleId: UUID?,
val title: ArgumentValue<
@NotBlank
@Size(min = 3, max = 100)
String
>,
val content: ArgumentValue<
@NotBlank
@Size(min = 10, max = 5000)
String
>,
val scheduledPublishDate: ArgumentValue<LocalDateTime?>
) {
val articleIdAsNotNull by lazy { articleId!! }
}
各フィールドを ArgumentValue でラップすることで、「送られてきたか?」「nullか?」「値があるか?」を区別可能になります。
Beanバリデーション
バリデーションもちゃんと効きます。
たとえば@NotBlankや@SizeをArgumentValueのジェネリクスの中に書いておけば、 値が送られてきたときだけバリデーションされるようになります。
undefinedのときはバリデーションはスキップされます。
まとめ
ArgumentValueを使うと、Spring for GraphQLでも以下のことが実現できました。
- 更新したいフィールドだけ送る
- 送られてこなかったフィールド(undefined)はそのまま保持
- nullのときは削除する