【Open API 考察】FE がAPI mock を作成するときに気をつけること
フロントエンドエンジニアの茶木です。甘党です。
API でデータを Getして表示したり、変更データを Putしてデータベースを更新したい要件があります。
フロントエンドは表示部分の実装を進め、APIの部分は Open API で mock を作ります。そして後ほど、バックエンドに mock どおりで API の実装が問題ないかレビューしてもらうという形をとっています。
先に結論
フロントエンドエンジニアが API mock を作成するときに気をつけると良いのは
- デザイン上の構造とデータ構造は必ずしも一致しないので留意する
- API mock はネストせずフラットに書くべきかを考慮する
API からGetしたデータを表示し、編集結果を Putする管理画面
それはなぜかという話をします。
例として、パンケーキを提供するお店のメニューを編集する管理画面を作るとしましょう。
メニューの名前と、トッピングとアイスに何がつけられるパンケーキなのかを設定できます。
たとえばこんな感じのメニュー画面です。
素直に考えた TypeScript 型
interface Props {
id: string;
name: string;
topping: {
butter: boolean;
syrup: boolean;
cream: boolean;
jam: boolean;
};
iceCream: {
vanilla: boolean;
strawberry: boolean;
};
}
デザインを見て素直に考えると、id
と商品名( name
)それにトッピング( topping
)とセットアイス( iceCream
)でグルーピングするのが良さそうです。トッピングの保存ボタンとセットアイスの保存ボタンを押したときに、PutAPI を呼び出すことを考えても、トッピング、セットアイス単位のグルーピングは自然に見えます。
素直に考えた Open API
components:
schemas:
Topping:
type: object
title: Topping
additionalProperties: false
properties:
butter: # バター
type: boolean;
syrup: # シロップ
type: boolean;
cream: # クリーム
type: boolean;
jam: # ジャム;
required:
- butter
- syrup
- cream
- jam
IceCream:
type: object
title: IceCream
additionalProperties: false
properties:
vanilla: # バニラ
type: boolean;
strawberry: # イチゴ
type: boolean;
required:
- vanilla
- strawberry
つづいて mock API 用に、同じようなデータ構造を考えると、Topping
と IceCream
という Object 型がそれぞれあると良さそうですね。
GetResponse:
title: GetResponse
type: object
additionalProperties: false
properties:
id:
type: string
name:
type: string
topping:
$ref: '#/components/schemas/Topping'
ice_cream:
$ref: '#/components/schemas/IceCream'
required:
- id
- name
- topping
- ice_cream
PutRequest:
title: GetResponse
type: object
additionalProperties: false
properties:
id:
type: string
name:
type: string
topping:
$ref: '#/components/schemas/Topping'
ice_cream:
$ref: '#/components/schemas/IceCream'
Topping
と IceCream
に id
と name
を加えて GetAPIの取得データの型( GetResponse
)と PutAPIの更新データの型( PutRequest
)ができました。GetAPI はページ表示に使うため全データを取得します。よって、各 props は required です。PutAPI は更新したい props だけを指定して送信する想定のため、 required ではありません。
APIの構造化を進めすぎると仕様変更に弱い
これで、全く問題ないように見えます。
しかし、ここで仕様変更があったとしましょう。
トッピングというカテゴリーを、基本とスペシャルに分ける、セットアイスというカテゴリーは廃止して、スペシャルトッピングに組み入れたい。
といった内容です。これはありそう。
この変更依頼では、PutAPI で更新できる単位が、トッピングとセットアイスで固定していたために、変更後の基本トッピングやスペシャルトッピング単位での PutAPI がコールできません。GetAPI も、もはや意味のない構造のネストを返します。
こうなると、API の更新が必要になります。
設計を考え直す
GetResponse:
title: GetResponse
type: object
additionalProperties: false
properties:
id:
type: string
name:
type: string
butter: # バター
type: boolean;
syrup: # シロップ
type: boolean;
cream: # クリーム
type: boolean;
jam: # ジャム;
type: boolean;
vanilla_ice_cream: # バニラアイス
type: boolean;
strawberry_ice_cream: # ストロベリーアイス
type: boolean;
required:
- id
- name
- butter
- syrup
- cream
- jam
- vanilla_ice_cream
- strawberry_ice_cream
PutRequest:
title: GetResponse
type: object
additionalProperties: false
properties:
id:
type: string
name:
type: string
butter: # バター
type: boolean;
syrup: # シロップ
type: boolean;
cream: # クリーム
type: boolean;
jam: # ジャム;
type: boolean;
vanilla_ice_cream: # バニラアイス
type: boolean;
strawberry_ice_cream: # ストロベリーアイス
type: boolean;
ネストせずフラットに書き直しました。これにより個別の props の更新が可能になりました。複数の props を更新したい場合でも必要なものを全てセットすれば良いのです。これであれば、取得/更新するデータが変わらない限りは API の変更は必要ありません。
なお、必要であれば、基本トッピングやスペシャルトッピングといった構造体への組み立てはフロントエンドで取得後に行います。
フロントエンドエンジニアが考えるべきこと
- 境界面での変更はフロントエンドで吸収する
(APIの変更は、フロントエンドの変更よりやりにくいことを知る) - API mock を作る段階で、更新性が高い設計になっているか考える
- ネストを使う強い理由がなければフラット化を考える
別解
少し結論がブレますが、フラット化より前に別解がこのケースではありそうです。バターもクリームもアイスも一律にトッピングとみなして、トッピング( Topping )の配列 を用意することが最適かもしれません。バナナやチョコレートソースなど、新トッピングが登場することはあるでしょうから、任意のトッピングを追加・編集できる構造が望ましいです。
components:
schemas:
Topping:
title: Topping
type: object
properties:
key: # butter など
type: string;
label: # バター など
type: string;
value:
type: boolean;
required:
- key
- label
- value
GetResponse:
title: GetResponse
type: object
additionalProperties: false
properties:
id:
type: string
name:
type: string
toppings:
type: array
items:
$ref: '#/components/schemas/Topping'
required:
- id
- name
- toppings
PutRequest:
title: GetResponse
type: object
additionalProperties: false
properties:
id:
type: string
name:
type: string
toppings:
type: array
items:
$ref: '#/components/schemas/Topping'
開発上考えるべきこと
フロントエンドエンジニアの癖
フロントエンドエンジニアは(自分は)デザインからDOMを想像する過程で、ツリー構造で理解を試みる傾向が強いので、そのままロジックに移していいか一考が必要だと思いました。
- デザイン上の構造とデータ構造が、必ずしも一致しないことに留意すること
- デザイン上の構造とデータ構造の差異を吸収するのはフロントエンドの使命
これらを、忘れないようにしようと思いました。
見た目から構造を考えてはいけない
そもそも、見た目から構造を考えるのは良くなくて、要件や、さらに手前の実現したいシナリオを理解が先にできるのが望ましく、その上で、最適な設計があるのだと考えました。
開発のお悩み、フロントエンドから解決しませんか?
あなたのチームのお悩みはなんですか?
「バックエンドエンジニアにフロントエンドまで任せてしまっている」
「デザイナーに主業務以外も任せてしまっている」
「すべての手が足りず細かいことまで手が回らない」
役割や領域を適切に捉えてカバーし、チーム全体の生産性と品質をアップするお手伝いをします。
フロントエンド開発に関わるお困りごとがあれば、まずは一度お気軽に Gaji-Labo にお声がけください。
オンラインでのヒアリングとフルリモートでのプロセス支援に対応しています。
リモートワーク対応のパートナーをお探しの場合もぜひ弊社にお問い合わせください!
お悩み相談はこちらから!