C++ APIスタイル(データ駆動型API)
今、「C++のためのAPIデザイン」を読んでます。 API機能を表現するための方法(スタイル)についてまとめてみました。
- 作者: マーティン・レディ,Martin Reddy,三宅陽一郎,ホジソンますみ
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2012/11/02
- メディア: 大型本
- 購入: 4人 クリック: 106回
- この商品を含むブログ (11件) を見る
APIスタイルについて・・・
この4つのスタイルのAPIを解説し、状況によって適切なスタイルの 選び方を提示します。
この記事では、以下ののAPIスタイルを解説します。
- データ駆動型API
データ駆動型API
データ駆動型APIとは・・・
データ駆動型プログラムとは、異なる入力データを供給する事で、 実行するごとに異なる操作を行うものです。
例えば、ディスク上に保存された命令実行リストを格納したファイル名を、 受け取るだけのプログラムがこの例です。
この手法がAPI設計にインパクトをもたらすのは、 様々なメソッド呼び出しを、提供するオブジェクトの集合に依存するのではなく、 名前付き命令や、名前付き、引数のディクショナリを受け取るもっと一般的な、 ルーチンを提供できるからです。
これは、メッセージ伝達APIまたは、イベントベースAPIと呼ばれます。 以下の関数の呼び出し例では、この種のAPIと標準なC/C++の呼び出しとの違いを示します。
- func(obj, a, b, c): フラットなCスタイルの関数
- obj.func(a, b, c): オブジェクト指向のC++の関数
- send("func", a, b, c): パラメータ付きのデータ駆動型関数
- send("func", dict(arg1 = a, arg2 = b, arg3 = c):名前付き引数のディクショナ付きデータ駆動型関数(疑似コード)
具体的な例として、データ駆動型モデルでスタックの例を再設計してみます。
class Stack { public: Stack(); Result Command(const std::string &command, const ArgList &args); };
このシンプルなクラスは複数な操作を行うのに使用されます。
stack = new Stack(); if (stack) { stack->command("Push", ArgList().Add("value", 10)); stack->command("Push", ArgList().Add("value", 3)); Stack::Result result = stack->command("IsEmpty"); while (!result->convertToBool()) { stack->command("Pop"); result = stack->command("IsEmpty"); } delete stack; }
これは文字列データで指定した、複数入力をサポートする 単一メソッドCommandで個々のメソッドを置き換えています。
各種コマンドを格納したテキストファイルの内容をパースし、 実行するプログラムを順に記述することは簡単です。
# データ駆動型スタックAPIの入力ファイル Push value:10 Push value:3 Pop Pop
データ駆動型スタックAPIを使って、このデータを受け取るプログラムは、 各行のスペースで区切られた最初の文字列を取るだけです。 (便宜上、#はコメントとして無視する) その後、コマンドに続くスペースで区切られた文字列に対して、 ArgListの構造体を作成します。 それから、Stack::Command()を実行し、ファイルの残りの処理を実行します。
このプログラムは異なるテキストファイルを共有する事で、 大幅に異なるスタック動作を実行できます。 しかも、プログラムを再コンパイルする必要もありません。
データ駆動型のWebサービス
APIによってコマンドがサーバに送られ、オプションとして結果をクライアントに返す クライアント/サーバ・アプリケーションをはじめとしたステートレスな、 コミニケーションチャネルには、データ駆動型が特に適しています。
また、疎結合コンポーネント間でメッセージを伝達する場合にも有効です。
中でも、Webサービスはデータ駆動型APIで表現するのに最適です。 Webサービスはクエリをパラメータの集合と共に、URLを送信したり、 JSONまたはXMLなど、構造化形式のメッセージを送ったりしてアクセスします。
例えば、ソーシャルサイトのDiggは、ユーザーがDigg.comのWebサービスと やり取りできるAPIをサポートしています。 具体例として、ある記事の詳細を読みたいユーザーのために、拡張情報を 返すAPIを提供しています。
これは以下の形式で、HTTPのGETリクエストを送信することで呼び出します。
http://service.digg.com/1.0/endpoint?method=digg.getinfo&digg_id=id
これは先程、提示したデータ駆動型APIにもうまく当てはまります。 先の例では、このようなHTTPリクエストを以下のように呼び出す事ができます。
digg = DiggWebServie(); digg->Request("digg.getinfo", ArgList().Add("digg_id", id));
データ駆動型APIのメリット
データ駆動型APIのメリットを以下にあげます。
・プログラムのビジネスロジックを編集可能なデータファイルに抽象化 つまり、実行ファイルを再コンパイルせずに、プログラムの動作を修正できます。 しかも、ユーザーがこのデータファイルを簡単に記述できるように、 別のデザインツールをサポートする事もできます。
・将来的なAPIの変更に対して十分に対応できる コマンドの追加や、削除、変更を行っても、多くの場合、 パブリックAPIメソッドのシグネイチャに影響がありません。
例えば、データ駆動型のスタックAPIを例にとってみましょう。 このAPIの新バージョンでは、Topコマンドを追加し、Pushコマンドを拡張し、 複数の値を受け取って、各値を順番にスタックにプッシュするようにしたとしましょう。
stack = new Stack(); stack->Command("Push", ArgList().Add("value", 10)..Add("value", 3)); Stack::Result result = stack->Command("Top"); int top = result.ToInt();
この新機能を追加しても、ヘッダファイルの関数シグネイチャには まったく何も変化がありません。 サポートした文字列と、Commandリストに渡す引数を変更しただけです。 この性質のため、データ駆動型APIでは、後方互換性を保ちながら、 変更を行う事がずっと簡単になります。
・テストをサポートしやすい これは個別のテストプログラムやルーチンを、大量に記述して行う代わりに、 自動でAPIテストを行うテクニックです。
具体的には、実行する一連のコマンドや、チェックすべきアサートを格納した、 ファイルを読み込むデータ駆動型プログラムを用意するだけです。 新規テスト作成にコンパイルの必要がないため、テスト開発の繰り返しが 早くできるし、C++開発スキルがあまりないQAエンジニアでも、テストを記述できます。
IsEmpty => True // スタックは空 Push value:10 Push value:3 IsEmpty => False // 2つの要素を持つので空ではない Pop => 3 IsEmpty => False // 1つの要素を持つので空ではない Pop => 10 IsEmpty => True // スタックは空 Pop => NULL // 空のスタックをPopするとエラーになる
このテストプログラムは、データファイルからスタックコマンドを読み取った、 以前のプログラムに非常によく似ています。 主な違いは、[=>]サポートを追加して、Stack::Commandの返り値の、 結果をチェック出来るようにしたことです。 たったこれだけの事で、あなたのAPIに対して、無数のデータ駆動型テストを 作成できる柔軟性のある、テストフレームワークに出来上がっています。
データ駆動型APIのデメリット
データ駆動型APIのデメリットを以下にあげます。
・実行時のコストがかかる このAPIのシンプルさと安定性には実行時のコストが付いてまわります。 これはコマンド名の文字列を呼び出すために、適切な内部ルーチンを検索する オーバーヘッドが余計にかかるためです。
・ユーザーがパブリックヘッダを見ただけでは機能が分からない ユーザーがパブリックヘッダを見るだけでは、インターフェイスによって、 どんな機能とセマンティックが提供されているのかを、把握する事はできません。 とはいえ、サポートされているコマンドと適切な引数のリストを明示した、 優れたAPIドキュメントを用意すれば、このデメリットを十分に補う事ができます。
・インターフェイスのコンパイル時チェックをしてもメリットがない パラメータの解析と、型チェックはAPI開発で行う事になります。 そこでコードをテストし、重要な動作を壊さないことを確認するのは、 開発者の型にかかるため、開発者の責任は大きくなります。