SSブログ
API仕様を書く ブログトップ

API仕様を書く(Mercari Engineering Blog編) [API仕様を書く]

API仕様を書く」と題して一連の記事を書きましたが、gRPCに関する内容を改めてMercari Engineering Blogとして書きました。



コメント(0) 

API仕様を書く(まとめ) [API仕様を書く]

「API仕様を書く」として私自身の過去の経験を書いたものを読みやすく並べてみました。
ちょうど同じような内容が『A Philosophy of Software Design』に書かれていました。

A Philosophy of Software Design

A Philosophy of Software Design

  • 作者: John Ousterhout
  • 出版社/メーカー: Yaknyam Press
  • 発売日: 2018/04/06
  • メディア: ペーパーバック

関連する章は、第12章「Why Write Comments? The Four Execuses」と第15章「Write The Comments First」です。第12章では、コメントを書かない理由として多くのソフトウェアエンジニアが挙げる理由について反証しています。第15章ではコメントを最初に書くことの有用性を説いています。ここでのコメントは、私が述べているAPI仕様に関する部分も含まれています。

翻訳はされないようなのですが、分かり易い英語で書かれていますので、ぜひ読まれることをお勧めします。

コメント(0) 

API仕様を書く(7) [API仕様を書く]

「API仕様を書く(6)ー gRPC protoファイル(2) ー」からの続き)

きちんとしたAPI仕様を書いていない場合、そのAPIのテストコードは当然のことながら、正常ケースだけだったり、不正なパラメータが渡されたときにどのように振る舞うかは実装のソースコードを見ないと分からなかったりします。さらに、「エラー翻訳」(「例外翻訳」)を行っていない実装は、いつのまにか返されるエラーが変わってしまっていることも起こり得ます。

おそらく多くの大学ではAPI仕様の書き方も含めてAPI設計の教育は行われていないと思います。そして、社会人となってから、個々のソフトウェアエンジニアがAPIの仕様をどの程度記述するかは、その人が属したソフトウェア開発組織の文化に影響を受けると思います。

私自身、gRPCを用いたソフトウェア開発では、前職のソラミツが初めてでした。そこでは、protoファイルには何も仕様が書かれていない理由は、それが大学の学生が作成したものだからだと思っていました。しかし、それは学生が書いたからではなく、そもそもAPI仕様を書くことを行ったことがない人達が書いたものだったからです。つまり、企業でソフトウェア開発をしているソフトウェアエンジニアであっても、仕様を書かない人が圧倒的に多いのではないかということです。

リコーに勤務していた頃は、さまざまなAPIをレビューしましたが、やはりきちんと書いてこないソフトウェアエンジニアが圧倒的に多かったです。その中でも、NDAを結んだサードパーティへ提供するAPIなのに、これはないだろうとう不完全なAPIが多くありました。

不備が多いAPI仕様は、不具合が多いAPI実装を生み出し、そして、サードパーティから問い合わせが多くなり、結果として長期的な開発コストを増加させます。これは、サードパーティへ提供するソフトウェアではなくても、会社内で閉じているソフトウェアでも、長期的な開発コストを増加させる結果となります。

マイケル・C・フェザーズの言葉を置き換えると次のようになるかと思います(「ネーミング」を「API仕様」と読み替えてます)(『レガシーコード改善ガイド』)。
API仕様は設計の中心である。優れたAPI仕様はシステムの理解を助け、作業を容易にする。しかし、貧弱なAPI仕様はシステムの理解を妨げ、後でシステムを扱うプログラマに辛い日々を送らせる。
(おわり)


コメント(0) 

API仕様を書く(6)ー gRPC protoファイル(2) ー [API仕様を書く]

(「API仕様を書く(5)ー gRPC protoファイル ー」からの続き)

gRPCから返すコードについては、Go言語用のこちらの定義が分かりやすいです。個々のコードは定義を読めば使い方は分かるかと思います。

API仕様には、gRPCで提供するサービスが提供する機能に応じて、どのような場合にどのコードが返されるかを書く必要があります。ただし、UnknownInternalに関しては注意が必要です。

Go言語のgRPC用のミドルウェアでは、statusパッケージ※1を使わずに生成したerrorを返すと、コードとしてUnknownが返されます。たとえば、DBへのアクセスして返されたerrorをそのままRPCのerrorとして返すと、コードはUnknownとなります。つまり、Unknownとは適切にコードが指定されなかったことを意味します。RPCが提供する機能に対する適切なコードを返すためには、「エラー翻訳」(Javaで言うところの「例外翻訳」)を行う必要があります。適切なエラー翻訳を行うためには、RPCが提供する機能に対して概念的に正しいコードを仕様に定義する必要があります。
※1 google.golang.org/grpc/status
※2 『Effective Java 第3版』の「項目 73 抽象概念に適した例外をスローする」

返すコードとしてのでInternalは、呼び出した側の問題ではなく、呼び出された側の何らかの不変式(invariant)が成立していないときに返します。言い換えると、設計上のバグと考えられるものはInternalを返すことになります。Java言語で言えばAssertionErrorが明示的にスローされるような場合です(『API設計の基礎』の「第3章 防御的プログラミング」)。

UnknownInternalは、どのRPCでも返される可能性があるので、個別のRPCの説明(前の記事の③)に書く必要はなく、サービス全体の説明(前の記事の①)に書けばよいと思います。どちらも、「呼び出し側の問題ではなく、呼び出された側の実装の問題なので、開発者への報告を求める」の旨が書かれていればよいかと思います。

(続く予定)

コメント(0) 

API仕様を書く(5)ー gRPC protoファイル ー [API仕様を書く]

(「API仕様を書く(4)」からの続き)

RPCの実装も通常のライブラリを作成するように「防御的プログラミング」を必要とします。すなわち、以下の状態に正しく対処する必要があります
  • パラメータ値不正
  • 呼び出し順序不正
  • 設計ロジックの誤り
最初の二つは呼び出した側の誤りのですので、そのような不正呼び出しに対して、どのようなエラーを返すかを記述する必要があります。三つ目は設計ロジックの誤りです(これらの三つの詳細な説明については、『API設計の基礎』の「第3章 防御的プログラミング」を参照してください)。

gRPCのprotoファイルの例として、https://grpc.io/docs/guides/ には次のようなサンプルが掲載されています。
// The greeter service definition. ①
service Greeter {
  // Sends a greeting ②
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name. ③
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings ④
message HelloReply {
  string message = 1;
}
番号(①、②、③、④)は私が説明用につけたものです。

①には、定義するサービスの説明を書く必要があります。この例では、RPCが一つしかないですが、通常は複数のRPC定義が書かれますので、サービスが提供する機能の概要を書く必要があります。サービスによっては、数行ではなく、10行以上の説明になることもあるかと思います。

②には、簡単にRPCの説明が書いてあれば十分かと思います。なぜなら、RPCの細かな振る舞いやリクエスパラメータにおける事前条件を説明しようとすると、パラメータである構造体やレスポンスである構造体のフィールドが同じ箇所に書かれていないので、②に書くには不適切かと思います。実際、私が仕事で書いているマイクロサービスにはRPCの定義が30個以上あります。

③は、RPCに対応したリクエストパラメータの構造体定義ですので、そのRPCの振る舞いを書くのはこの部分が適切かと思います。さらに、以下のことも書き加える必要があります。
  • リクエストの構造体の各フィールドに許される値
  • 許されない値が設定されていた場合に返されるエラー
たとえば、Hellonameフィールドが空を許さないのであれば、空が指定されたらどのようなコードが返されるかと記載する必要があります。

不正なパラメータの場合、単純にリクエストのフィールドの値が仕様で要求される形式や値を満たさないのであれば、InvalidArgumentでよいかと思います。そうではなくて、たとえば指定されたデータがデータベースに無いのであれば、NotFoundかもしれません。

gRPCには成功のOKを含めて標準のコードが17個定義されています(Go言語用の定義はこちら)。どのような場合に、どのようなエラーコードを返すかは、きちんと設計し、かつ、API仕様に明確に記述しなければなりません。つまり、③の部分に明確に記述する必要があるということです。

上記の例では、実際には何も書かれていません。nameが空でもよいのか、空を許すとしたらそれは何を意味することになるのか、空を許さないとしたら空の場合どのエラーコードが返されるのか、呼び出すのに認証は必要ないのかとかです。

④のレスポンスについては、理解するために必要な説明を書く必要があります。自明の場合には、何も書かなくてもよいかもしれません。

(続く予定)

コメント(0) 

API仕様を書く(4) [API仕様を書く]

(「API仕様を書く(3)」からの続き)

2017年8月末でリコーを退職して、ソラミツ社で働き始めました。技術的にはgRPCを使ったサーバー側の開発に加わることになりました。

gPRCに触れたのはその時が初めてですが、RPC(Remote Procedure Call)そのものは、Xerox社のCourierとよばれるRPCに1984年から接していましたし、後にSunのRPCを使ったツール開発(MessagingToolと分散コンパイルツール)も行っています。Courierは、XNS(Xerox Network Systems)の各種サーバーのプロトコルを記述するのにも使われており、プロトコル仕様はかなり丁寧に書かれていたと記憶しています。

gRPCは、RPCの定義を.protoファイルに書いてprotocでコンパイルしてスタブを生成します。RPCはその名前が示す通り、Procedure Callであり、Procedureを定義する訳です。呼び出しのパラメータ、呼び出し結果のレスポンスなどをstructとして定義します。また、エラーを通知するためにステータスコードを返すことができるようになっています。ステータスコードは、Javaに例えるとメソッドがスローする例外に相当します。

開発されていたサーバーのgRPCの定義である.protoファイルの中を見ると、何も書かれていませんでした。Javaで例えると、「公開APIのクラスやインタフェースの定義が書かれている.javaファイルに一切Javadocが書かれていない」という状態でした。

『Effective Java 第2版』を読まれたことがあるエンジニアであれば、そのようなクラスやインタフェースは公開APIとしては不適切であることは認識できると思います。『Effective Java 第2版』で該当する項目と章は以下の通りです。
  • 項目44 すべての公開API 要素に対してドキュメントコメントを書く
  • 第9章 例外
もちろん、『Effective Java』はJavaに関する内容なので、.protoファイルでは、そのエッセンスだけを適用して読み替える必要があります。簡単にまとめると、以下のことをAPI仕様として書く必要があります。
  1. 各PRCの説明
  2. 各PRCのリクエストパラメータとレスポンスパラメータの説明
  3. リクエストパラメータの制約(事前条件)とそれに違反したときに返されるステータスコードの説明
  4. RPC呼び出しの制約(事前条件)とそれに違反したときに返されるステータスコードの説明
  5. RPCを実行したときに起きる可能性のあるエラーとそれに対して返されるステータスコードの説明
何も書かれていなかったので、サーバー側のシステムを理解することを目的として、私自身ですべての仕様を.protoファイルの中にコメントとして書きました。仕様を書きながら、不備も多く見つけましたし、gRPCを直接呼び出してテストするコードを書いて、必要あればサーバー側のコードを修正したりもしました。

上記の1.から5.までについては、次回もう少し詳しく書きたいと思います。

(続く予定)

コメント(0) 

API仕様を書く(3) [API仕様を書く]

(「API仕様を書く(2)」からの続き)

私自身が開発のグループリーダーであった1701Gが組織として存在していた2013年7月から2015年5月までの期間を除けば、リコーでの8年間は、自分で何かを設計することは非常に少なく、誰かが設計したものをレビューすることが圧倒的に多かったです(残念ながら1701Gのときも、私自身はレビューやデバッグをすることはあっても、自分で設計や実装まで行うことは少なかったです)。

2009年9月にリコーで働き始めたのですが、当時はある大規模なソフトウェア開発がJavaで行われていました。しかし、ソフトウェア開発経験が浅いソフトウェアエンジニアが大量に投入されていて、誰も『プログラミング言語Java 第4版』も『Effective Java 第2版』を読んだことがない状況でした。それに加えて、マルチスレッドプログラミングが行われており、私がレビューしたほとんどのコードは間違っていました(「マルチスレッドプログラミングにおける重要な4要件」)。それで、やはりきちんと学習させる必要があるということで、リコーでもJava研修を始めることにしました。

1701Gの期間を除けば、様々なソフトウェア開発組織の新規APIのレビューやコードをのレビューを行っていたのですが、振り返ってみると、やはりAPI仕様を書くことは、多くの開発者が不得意としていたという印象があります。特に、NDAを結んで外部のサードベンダーに公開するSDKのAPIに関しては、使う側のことを考慮したAPIに出会うことは少なく、様々な修正を指導していました。

今から言えることは、きちんとしたAPI仕様を書かせるには、「場」である開発の現場で指導し続けなければならないということです。そして、「指導し続ける」には、以下の条件が揃っている必要があると思います。
  1. 指導対象者のエンジニアより、開発組織内で高い地位にあること
  2. 自分の部下を育成するという意味で指導を根気よく続けられること
同じ年代のエンジニアに何かを注意したり指導したりするのは、なかなか難しいです。さらに、別の組織やグループのエンジニアに対して指導するのはさらに難しいです。そのため、やはり1.が重要だということです。講演で、「どのようにして指導してきたのですか」と聞かれることがあるのですが、「開発部長としての立場で強制的に指導していました」と答えることが多いです。それは、API仕様を書くことだけでなく、テストファーストで開発することや、不具合が発生したら再現テストを最初に書かせるといったさまざまなソフトウェア開発の側面での指導が含まれます。

リコーでの最後の2年は、1.の条件は成立していなかったのですが、私のレビューを積極的に受けていたのは、その多くが私のJava研修の修了生達でした。

(続く予定)

コメント(0) 

API仕様を書く(2) [API仕様を書く]

(「API仕様を書く(1)」からの続き)

2003年からリコーに転職する2009年まで従事したデジタル複合機のコントローラソフトウェア開発プロジェクト(ピーク時は約100名のソフトウェアエンジニアが従事)では、私が提唱したある方式に基づいて完全なテスト駆動開発を行っていました。そのソフトウェア開発もレイヤ構成のソフトウェアであり、多くはプロセスとして実装され、プロセス間通信をCORBAで行っていました。

今日で言えば、マイクロサービス化してサービス間で通信してシステムを実現しているようなものです。各プロセスは下位層のハードウェアからのイベントと上位層のUIからユーザ指示のイベントの両方を処理する必要があるため、個々のプロセス内ではマルチスレッドプログラミングが行われているというものでした。品質を担保するために、当時としては複写機業界ではトップクラスのテスト駆動開発を行っていました(「マルチスレッドプログラミングにおける重要な4要件」)。

各サービス(プロセス)が提供するAPI仕様は、プロジェクト全体でかなりきちんと書かれていました。このプロジェクトでは、私も中核となる最も複雑な処理を行うサービスを担当して、API仕様を書いて、実装して、テストを書いて、そしてマルチスレッドのデッドロックやrace conditionをひたすらデバッグしていました(さらに付け加えると、開発部門の部長もしていました)。

このプロジェクトでは、API仕様を書く書かないの選択肢は個々のサービス担当者に委ねられることはなく、プロジェクト全体で書くことが強制されてたような気がします(あるいは、ある程度私が強制させていたのかもしれませんが、もうあまり覚えていません)。

振り返ってみると、1984年から2009年までその多くを富士ゼロックスグループで過ごし
、自分自身も多くのAPI仕様を書いてきたので、自分が担当するモジュール、ライブラリ、サービスのAPI仕様を、「利用者の視点を意識して」書いてから実装を行うことが、ソフトウェアエンジニアとして当たり前だと思っていました。そして、一緒に仕事をした多くのソフトウェアエンジニアが同じだったと思います。

2009年8月に富士ゼロックス情報システムを退職して、リコーに転職したのですが、そこは同じ複写機業界でありながら、ソフトウェア開発に関しては全く違う世界がありました。

(続く予定)

コメント(0) 

API仕様を書く(1) [API仕様を書く]

株式会社メルペイに入社して、3か月が過ぎました。1984年に社会人となってからさまざまなソフトウェア開発に従事してきましたが、今日でいうところの「Backend Engineer」というのは前職のソラミツ社で少しかじった程度でした。現在は、あるマイクロサービスの開発を担当しており、この3か月間の半分以上は、そのマイクロサービスのAPI仕様を書くことに費やしていました。マイクロサービスの通信は、gRPCで行いますので、API仕様はその.protoファイルにコメントの形式で書いています。

実装をほとんどせずにAPI仕様を書くということをいつ頃からやった経験があるのかと振り返って見ると、1993年から開発に従事したFuji Xerox DocuStation IM 200です。レイヤ構成のアーキテクチャで、最下位層はハードウェアを抽象化したAPIを提供するMCA(Machine Control Architecture)と呼ぶものでした。その層では、IIT(Image Input Terminal:スキャナー)、IOT(Image Output Terminal:プリンター)、Fax、操作パネルといったデバイスに対してハードウェアに依存しない抽象的なAPIを提供するのがMCAの目標でした。その中で、全体の共通仕様とIITとIOTの制御ソフトウェアのAPI仕様を英語で書きました(Xerox社へ提供予定のAPIだったので)。当初、私はAPI仕様だけを書いていたのですが、実装担当者によるAPI仕様の実装が徐々に難しくなっていったため、代わりに私が実装することになって二か月で全部を再実装しました。

その後、MCAの上でコピーサービスとFaxサービスを、私を含めて4人のエンジニアで4週間でプロトタイプして動作させることができたので、そのプロトタイプを捨てて、すべてをきちんと設計することになりました。私は、MCAの上でMAE(Multifunction Application Environment)と呼ぶ層を担当することになり、そのAPI仕様のドラフトを書き上げました。それから実装を始めたのですが、API仕様があるのでテストコードは他の若いエンジニアに書いてもらいながら、彼女と二人でMAEの開発を進めました。

ここまでの経験は、最初にAPI仕様を書いてから、それを実装していくというものです。実装しながら不都合があれば、API仕様はもちろん修正されていきます。

API仕様を書いて、自分では一行も実装を書かない経験をしたのは、現在の富士ゼロックス社のデジタル複合機のコントローラソフトウェアで使われているC++用のライブラリ(こちら)です。2000年のことです。そのライブラリのAPI仕様(日本語)は、本来の動作仕様に加えてかなり防御的に書きました。つまり、不正なパラメータ値を渡した場合の挙動をデバッグ版とリリース版に関して細かく書きました(参考:「API設計の基礎」)。このライブラリの実装は全く行いませんでしたが、実装のコードは全部レビューしました。そして、そのライブラリに関する「Programmer's Guide」(A4で約100ページ)も後に書いています。
※ Webster, NYに住んでいた2002年の暮れにXerox社との共同プロジェクトが中止になって、とても暇になってしまったので書きました。

(続く予定)

コメント(0) 
API仕様を書く ブログトップ