So-net無料ブログ作成
  • ブログをはじめる
  • ログイン

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) 

通勤電車の書斎化(2) [朝型]

4年前に「通勤電車の書斎化」を書いています。今年の6月から株式会社メルペイでソフトウェアエンジニアとして働き始めてからは、通勤時間が往復で約4時間となっており、今までの人生の中で最も長い通勤時間となっています。

朝は、5時40分ごろに自宅を出て、最寄り駅から始発に乗車しています。始発に乗車して、2駅で1回目の乗り換えをします。乗り換えて各駅停車に乗車することで確実に座れるのですが、その代わり、次の乗り換えまで1時間ほど電車に乗車しています。2回目の乗り換えは青山一丁目で、そこから大江戸線で1駅で六本木駅です。会社に7時30分前後に到着します。以前は、出社前にスターバックスに寄ることがあったのですが、今はそのまま出社します。

過去、何度か腰を痛めたことがあるのが原因かどうか分かりませんが、ちょうどメルペイで働き始める前ぐらいから腰部脊柱管狭窄症になり、座っているのは問題ないのですが、ずっと立っていたり、長く歩くのが困難な状況になっています。そのため、帰りも座って帰るために、六本木駅から逆方向に帰って、清澄白河駅で乗り換えています。その分、帰宅時間は長くなりますが、六本木駅からもほとんど座れて、自宅の最寄り駅まで座れます。帰りは、16時過ぎに退社するのですが、自宅に帰宅するのが18時30分頃になります。

結果として、通勤時間が往復4時間になっています。そのうち、電車の中でMacBookを広げて作業をするのが約2時間です。平日は、自宅の書斎で作業をすることはほとんどありませんが、通勤電車の中で2時間ほど作業することになります。先日までは、『Effective Java 第3版』の翻訳作業をしていました。
コメント(0) 

bytes.Bufferの実装の変更 [golang]

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

  • 作者: Alan A.A. Donovan
  • 出版社/メーカー: 丸善出版
  • 発売日: 2016/06/20
  • メディア: 単行本(ソフトカバー)

『プログラミング言語Go』の「6.6 カプセル化」でbytes.Bufferに関して次のような記述があります(193ページ)。
例として、bytes.Buffer型を考えてみてください。 この型は非常に短い文字列を蓄積するために頻繁に使われます。したがって、そのようによくある場面でのメモリ割り当てを避けるために、そのオブジェクト内に少し余分な領域を確保するのは有益な最適化です。 Bufferは構造体型なので、その領域は大文字で始まっていない名前を持つ[64]byte型の追加のフィールドの形式になります。このフィールドが追加されたとき、それは公開されなかったので、Bufferを使うbytesパッケージの外部のクライアントは、改善された性能を除いたどの変更にも気づきませんでした。
実際に、Go 1.11まではこの記述にあるような実装なのですが、次のGo 1.12ではこの[64]byte型の追加のフィールドは削除されるようです。修正のコミットは、こちらです。

コメント(0) 

『Effective Java 第3版』の翻訳作業が終わりました [本]

昨年の2月から原著『Effective Java Third Edition』の草稿のレビューが始まり、レビューが終わったのが11月で、昨年末には原著が発売されています。翻訳作業は、昨年の12月から始めて、すべての作業が今月初めに終了しました。今回も、翻訳および(索引作りも含めた)組版までLaTeXで行いました。

原著のレビューのときには気付かなかった多くの間違いは、翻訳作業を通してJoshua Bloch氏へ知らせており、原著の4th printing(第4刷)では修正されています(原著の正誤表は、こちらです)。

今回はもっと早く終わるかと思ったのですが、結局、約430時間を翻訳作業に費やしました。翻訳に先立っての原著のレビューは約42時間でした。

Amazon.co.jpでは、以下のように紹介されています。
Javaプログラマーにとって必読の定番書『Effective Java』の改訂第3版。

この第3版では、Java 8で新たに導入されたラムダとストリームに関する章が新規に追加されたほか、 オプショナル、インタフェースでのデフォルトメソッド、try-with-resources文、@SafeVarargsアノテーション、モジュールなどの機能を扱った項目を含み、第2版の78項目から90項目に増加しています。

今日ではJavaは大きく複雑になり、並列実行から、繰り返し、各種データの表現まで、多くの事柄に対して多様な抽象化を持ち合わせています。この大きさと複雑さを考えると、最新のベストプラクティスの指導書はなおさら重要です。

本書は多くのデザインパターンとイデオムを示すコード例を含んでおり、プログラミング言語Javaの正しい理解と、簡潔で明瞭で正確なソフトウェアの設計に役立つでしょう。
発売予定は10月31日です。予約注文は、Amazon.co.jpのこちらのページからできます。
コメント(0) 

「ソフトウェアエンジニアの心得」教育 [「ソフトウェアエンジニアの心得」講演]

かなり前(たぶん2000年の頃?)から行っている「ソフトウェアエンジニアの心得」の教育を日本総合システム社の新卒新人向けに行いました。リコーに勤務していた2009年から2017年までは、毎年ソフトウェア開発系の新卒新人向けに行っていた研修です。

IMG_0379.JPG
日本総合システム社近くの「小石川後楽園」


コメント(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) 

言語仕様とメモリモデル [プログラマー現役続行]

『Effective Java 第3版』の第11章「並行性」(あるいは、第2版の第10章「並行性」)を内容を理解するためには、Javaのメモリモデル(memory model)を理解する必要があります。『Effective Java 第3版』の翻訳原稿による補講でも「メモリモデルとは何か」という質問がありました。

マルチコアやマルチプロセッサを前提としてマルチスレッドプログラミングを言語仕様として提供する言語では、メモリモデルは言語仕様の一部とも言えます。Javaであれば、『The Java Language Specification』の17.4節の「Memory Model」、あるいは、『プログラミング言語Java第4版』の14.10節 「メモリモデル:同期とvolatile」に書かれています。それぞれ、22ページと5ページを費やして解説しています。

今日、マルチコアやマルチプロセッサを前提とした言語は、Javaだけではありません。たとえば、Go言語ではゴルーチン(goroutine)に関してのメモリモデルが、言語仕様であるThe Go Programming Language Specificationとは独立したThe Go Memory Modelとして定義されています。私自身は、C++でのプログラミングから離れてから久しいのですが、C++でもマルチスレッドプログラミングのサポートに伴いメモリモデルが定義されています。
※ 『プログラミング言語Go』では、「メモリモデル」という表現は使っていませんが、「happens before」関係に関しては、「8.4.1 バッファなしチャネル」、「9.1 競合状態」、「9.4 メモリの同期」で述べられています。

以前の記事「マルチスレッドプログラミングにおける重要な4要件」で述べた一番目の要件「きちんとしたレビュー」では、「マルチコアやマルチスレッドプログラミングのきちんとした経験および知識を持つ人が設計やコードをレビューしていること」と述べてします。ここでの「知識」には当然「メモリモデル」も含まれます。

JaSST Tokyo 2018の招待講演では、デジタル複合機のコントローラソフトウェア開発を4回行った経験に基づいて話をしたのですが、振り返ってみると、そのような開発に従事したからこそ、マルチスレッドプログラミングでの開発を経験できたと言えます。それらの開発は、C++もしくはGoによる開発でしたが、マルチスレッドプログラミングに関わる様々な問題(もしくは、ゴルーチンにとチャネルによる様々な問題)に直面し、デバッグを経験することで多くの知識と経験を得ることができました。

株式会社メルペイでbackend engineerとして働き始めて3か月が経過しましたが、(私のバックエンドの開発経験がまだ少ないからかもしれませんが)ウェブシステムでのバックエンド開発では、上記の一番目の要件を満足できるような開発経験を積むのは困難なのかなと感じ始めています。Go言語で開発していますが、メモリモデルを理解しないといけないようなプログラミングはほとんどしないので・・・私が今から経験する必要はないのですが、今の若い人達はいつ経験して知識を得るのだろうかと。

コメント(0)