昨日、各画面ごとに分担して並行開発をする話をしました。
これは、サーバはAPIだけを提供する疎結合なアーキテクチャーになっており、画面担当者は各自が自由に実装・テストしていけるvte.cxの環境だからこそ可能になるという話でした。
しかしこの方法でも、やり方を間違えると極端に生産性が下がり品質も悪くなります。
私はこの問題を甘く見ていました。
今日はこの話をしたいと思います。
#並行開発の落とし穴
実際の開発では、まずAPIモックを作成し、画面担当者がそれを使って実装している間に、サーバ担当者がAPIの内部実装を進めていくというやり方をします。
これは一見うまくいくように思えるのですが、実は大きな落とし穴があります。
最初のうちは開発が順調に進みますが、結合試験のときに大きな混乱が生じるのです。
#APIのI/Fが曖昧なことで起きる問題
それは、APIのI/Fがしっかり固まっていないことが原因です。
これまでの開発では、基本的にサーバサイドの開発者が画面も実装していため、画面とAPIとの結合で問題になることはありませんでした。
かつては画面とのI/Fなどを気にする必要もなかったのですが、今回のような疎結合環境においては画面とサービスで異なる担当者が別々に開発するため、APIのI/F設計が特に重要になります。
つまり、分業が前提になるため、APIのI/Fが曖昧だと大きな問題を引き起こすのです。
具体的には以下のような問題です。
- モックで作成したAPIと実際のサービスのAPIとの祖語が大きすぎることによる手戻りコストの増大。画面から呼ぶべきサービスが抜けてしまうことも。
- モックは基本的にスタティックであり、条件によって変化するような値を返すことをしないため、画面ではどうしても実装の漏れが生じてしまう
- サービス開発者は画面から呼びだされる詳細な仕様がわからないまま実装するので品質が悪くなる
#アウトサイドインTDDの導入
私はこれを解決するヒントはTDD(テスト駆動開発)にあると思っています。
APIのI/Fが曖昧なのは、モックのI/Fが最新ではなく不正確だからです。
そうではなく、テストファーストプログラミングのように、モックを本物のオブジェクトと見立てて、要求に変更があればアジャイル的に即座に対応していくような手法を取り入れることで解決します。これをアウトサイドインTDDといいます。
アウトサイドインTDDは、ユーザー要求やアーキテクチャ設計といった上位のインプットとTDDのプログラミングが断絶することがないように考えだされた手法です。
これが重視するのは、全体の要求を明確化することと、それをテストで記述し、そのテストによって開発を駆動させていくことです。
まず最初にアーキテクチャなどの全体構造の設計を行い、TDDの対象となるモックとそのAPIの振る舞いを明確化してからテストファーストのサイクルを回していきます。
また、モックが未実装状態でも全体設計を仮想的にテストで動かせる状態にした上で、TDDでモックを本物のサービスに置き換えていきます。
なので、画面側のリクエストは常に最新のAPIを対象にできますし、サーバ側のサービスも常に最新のAPIに対応していけます。
このように、全体整合の取れた状態になるため、前述したような不整合な状態で不適切に開発されるような拙い状態を防止できます。
#APIのI/Fを決定するのは画面かサービスか
でも、素朴な疑問が一つ残ります。
アウトサイドインTDDが重視するのは、全体の要求を明確化することでしたが、そもそもそれができる人は誰なのでしょうか?
APIのI/F設計を決める責務があるのは、画面の呼び出し側でしょうか、それともサーバのサービス側でしょうか?
サーバサイドの開発者がすべてを行なっていた時代では全体の青写真を把握している人がいました。画面側の要求もサーバ側の都合もサーバサイドの開発者がすべて承知したうえで設計していました。
ところが、分業が前提になると全体を把握する人がいなくなります。
その結果、画面はサーバサイドの都合を考えずに実装し、一方のサーバサイドでは画面からどういう要求が必要なのかよくわからないまま実装するようになります。
つまり、APIのI/Fをきっちり決めたくても画面もサービスも主導権を発揮できないのです。
vte.cxではサーバサイドの開発をフロントエンド開発の延長でできるようにしていると説明してきましたが、それはフロントエンド技術者が適任というわけではありません。むしろ、フロントエンド開発者はAPI設計に苦手意識を持っている方が多いと思われます。
このように、I/F設計については、クライアント側が担当すべきなのか、サーバ側なのかは悩ましいところです。
#Consumer-Driven Contract testingパターンの利用
現実的な方法は、画面側から具体的なI/Fの要求を聞き、サービス側でそれを論理的に設計していくというのがよいように思います。
単純なREST呼び出しでは解決できないようなケースがあればサービスのI/Fとして定義することもクライアント主導で可能でしょう。
また、I/Fさえ決まれば詳細設計はサーバ担当者に任せるといった具合に分業も可能になります。
ところで、Consumer-Driven Contract testing パターンというのをご存知でしょうか?
これは、従来バックエンドが決めていたAPIの仕様をフロントエンドが主導して要求を書くことでAPI仕様を決めていくスタイルです。
(参考) サービス分割時の複雑性に対処する: テスト戦略の話
- Consumer がある特定のリクエストに対応する期待するレスポンスを定義する。
- Provider と Consumer はその Contract について合意する。
- Provider は自身が継続的にその Contract を守れているか検証する
ただ、クライアントからの要求をすべて受け入れてそのまま実装するのではなく、I/F全体を見ながら適宜、抽象化や汎化といった高レベルの設計を実施していくことはサーバ担当者が責任もってやるべきでしょう。
リクルートテクノロジーズのフロントエンド開発 2016で紹介されているagreedは、Mock Server兼、テストクライアントになっており、Providerに対して継続的にContractを守れているかの検証ができるとのことです。
#結局、フロントエンドにバックエンドのテストを書かせればいいんじゃね?
Consumer-Driven Contract testingパターンはとてもよい手法だと思います。ですが、フロントエンドの開発者が本来作るべきソースコードやテストコードの他に別途Contractを作ってもらうのはちょっと気が引けます。それに、Pactなどを使いこなしてもらう必要がありますし、Contractの自動生成ツールを作るのもイマイチです。
そこで行きついた結論は、フロントエンドの開発者にバックエンドのモックを作ってもらい、さらに、テストコードを書いてもらうというものです。つまり、このテストコードがContractの代わりになるというわけです。
バックエンドのサービス開発者はテストが常にGreenになるように実装しなければなりません。
これであれば必要最小限の実装で済むのでフロントエンド開発者も納得してくれるはずです。
ちょうど同じことを考えて実践している人がいました。素晴らしい。
フロントエンドエンジニアがサーバーサイドエンジニアとペアを組む話
そこで、サーバーサイドのエンジニアがテーブル定義やモデルのロジックを考えて実装しているあいだに、フロントエンドエンジニアがAPIの仕様を考えて、せっかくなのでモックデータを返すところまで作ってしまうということをやったりします。モックとはいえ実際のAPIを叩きながら開発できるのでフロントエンドの実装が圧倒的にやりやすく、設計上の問題も早い段階で見つかりやすい(はず)です。
vte.cxのサーバサイドの実装例については後日説明したいと思います。
それでは、また明日。