こんにちは。Mikatus でサーバーサイドエンジニアをしている gypsy と申します。
この記事は Mikatus アドベントカレンダーの17日目です。
最初、「Scala + DDD + 関数型でポケモンをモデリングしたい」というタイトルで記事を書こうと思っていたのですが、モデリングしているうちに情熱が失われてしまったので別のことを書きます。
並び替え処理の悩み
最近、要素の並び替え処理(ToDo アプリのタスクの並び替えのような)を設計する機会があったのですが、なかなか納得のいく実装が見つけられないでいました。
最初に考えたのは、並び順を 1 からの連番として表現し、
// resourceId の並び順を 2 番目にするリクエスト
PUT path/to/resources/resourceId/sort
{
  "order": 2
}
みたいな感じで並び替え用の URI に PUT メソッドを投げ、並び順を指定する方法です。
そしてサーバサイドでは、
// E を 2 番目に移動する場合
A: 1      A
B: 2      E 割り込み
C: 3  =>  B 1つ繰り上げ
D: 4      C 1つ繰り上げ
E: 5      D 1つ繰り上げ
こんな感じで移動する順番以降の全リソースに対して並び順を変更します。
この実装でも一応並び替え自体は実現できるのですが、単一のリソースを対象に PUT を行っているのに、他のリソースにまで変更が及んでしまうのが気持ち悪いです。また、一度の PUT に対して複数のリソースが変更されるため、処理のコストが高くなってしまう可能性があります。(これに関してはバルクアップデート等の回避策があるため、問題にならないかもしれませんが)
妙案も思い付かずもやもやしていたのですが、開発グループのリーダーから「REST に従って並び替えを実装するなら、要素の位置を浮動小数点で表現すればよい」とアドバイスされ、これが面白く、自分の不満を解消するものだったので取り上げさせていただきます。(後で調べたら Trello もこの実装方法でした)
位置を浮動小数点で表現した場合の並び替え処理
要素の位置を浮動小数点で表現する、とはつまりこういうことです。
A:  65535.0
B: 131070.0
C: 196605.0
D: 262140.0
E: 327675.0
最初のやり方では、全体の中の相対的な順番を 1 からの連番で表現していたのに対し、こちらはその位置の絶対値となります。(ちなみに、この 65535 という数値は Trello を参考にしています)
では実際に、どのように並び替えを行うのか見ていきましょう。
リクエストは以下のような感じになります。
// resourceId の位置を 98302.5 にするリクエスト( = 並び順を 2 番目にするリクエスト)
PUT path/to/resources/resourceId
{
  “position”: 98302.5
}
先ほどのリクエストでは相対的な並び順を指定していましたが、今回は位置の絶対値となっています。指定している position は並び替えた後の前の要素と後ろの要素の中間の値です。ここでは 65535.0 と 131070.0 ですね。
では続いてサーバーサイド。
// E を 2 番目に移動する場合
A:  65535.0      A:  65535.0 変更なし
B: 131070.0      E:  98302.5 位置情報変更
C: 196605.0  =>  B: 131070.0 変更なし
D: 262140.0      C: 196605.0 変更なし
E: 327675.0      D: 262140.0 変更なし
Eの位置情報を変更しただけで、全体の並び順が変えられたことがわかります。
もう少し並び替えを行ってみましょう。
// C を 2 番目に移動する場合
A:  65535.0      A:  65535.0  変更なし
E:  98302.5      C:  81918.75 位置情報変更
B: 131070.0  =>  E:  98302.5  変更なし
C: 196605.0      B: 131070.0  変更なし
D: 262140.0      D: 262140.0  変更なし
// B を 最初に移動する場合
A:  65535.0       B:  32767.5  位置情報変更
C:  81918.75      A:  65535.0  変更なし
E:  98302.5   =>  C:  81918.75 変更なし
B: 131070.0       E:  98302.5  変更なし
D: 262140.0       D: 262140.0  変更なし
// B を 最後に移動する場合
B:  32767.5       A:  65535.0  変更なし
A:  65535.0       C:  81918.75 変更なし
C:  81918.75  =>  E:  98302.5  変更なし
E:  98302.5       D: 262140.0  変更なし
D: 262140.0       B: 327675.0  位置情報変更
先ほどは移動する度に以降の全リソースに対して並び順を変更する必要がありましたが、この方法であれば変更するのは対象のリソースのみで問題ありません。このように、連番の並び順ではなく位置情報を用いることで、単一のリソースを対象に PUT を行っているのに、他のリソースにまで変更が及んでしまう気持ち悪さは解消されました。また、1 度の PUT で複数変更される事によるコスト増大も起こりません。
すばらしい。
まとめ
RESTful な API の設計は難しいです。もちろん過度に REST に囚われてしまうのはアンチパターンだと思うのですが、できるなら REST っぽくしたい。そんな時には他のサービスがどう実現しているのか、探ってみると良い解決法が見つかるかもしれませんね。
