Author Archives: M. Y.

2017-05-17

API Blueprint で API 仕様書を書いて、配布用の HTML を自動生成する方法

(画像は API Blueprint の Web サイト より転載)

GMO アドパートナーズ グループ CTO 室の M. Y.(DevOps ネタ担当)です。今回は、API 開発時に使って便利だったツールの話をします。

きっかけ

最近、私が担当している広告関係のプロダクトに、お客様向けに公開する API を新規追加することになりました。この API はお客様側のエンジニアが利用するため、API 仕様書を作る必要があります。

過去の社内事例では、Word ファイルで API 仕様書を作成して、配布していました。しかし、Word ファイルでは差分を確認しづらいので、API 仕様自体のバージョン管理が大変です。そこで、今回は API 仕様書から配布用の HTML を自動生成することにしました。

API 仕様書を自動生成する技術としては Swagger が有名ですが、今回のプロダクトでは API Blueprint と Swagger を比較して、API Blueprint を採用しました。この記事では、実際の API に似せた具体例を示しつつ、API Blueprint の使い方をご紹介します。

今回の要件

今回のプロダクトでの要件は、次のようなものでした。クライアントやモックサーバの自動生成よりも、ドキュメント管理の敷居を下げることを重視しました。

  • 仕様書をテキストファイルに書いて、GitHub で変更履歴を管理したい
  • 一部機能の契約者のみに配布する、見た目の良いマニュアルを自動生成したい
  • 特定のツールに詳しくないメンバでも、少し学習すれば API を追加していけるようにしたい
  • API の数はそれほど多くない

API Blueprint と Swagger の比較

比較にあたり、まず仮の API 仕様を決めて、それぞれのフォーマットで実際に仕様書を書いてみました。そして、その仕様書を以下のツールにかけて動作確認しました。

その結果をもとに、いくつかの評価軸で両者を比較してみました。これは今回の要件に照らして比較したもので、Swagger の得意な分野(コード自動生成など)が一部抜けていることにご注意ください。

No. 評価軸 API Blueprint Swagger
1 言語仕様 Markdown を拡張した形式で書く(拡張子名は apib) YAML、JSON またはソースコード上のアノテーションで書く
2 編集方法 ローカルディスク上のファイルを好きなエディタで編集すると、Web アプリ(aglio)の表示が自動更新される ローカルディスク上のファイルをWeb アプリ(swagger-editor)で開いて編集し、表示を確認する
3 文法ミスの訂正 aglio のコマンドライン表示で警告される swagger-editor の編集画面で警告される
4 HTML ファイルの生成 ローカルで開ける単一の HTML ファイルを aglio で生成できる bootprint または swagger-codegen-cli で生成できるが、swagger-editor や swagger-ui よりも見た目が劣る
5 記載の柔軟性 言語仕様に含まれない情報も Markdown 形式で自由に記載できる 言語仕様に含まれない情報の表現力は低い

ドキュメントの編集のしやすさ(No.1〜3)、および単一の HTML ファイルを生成する場合の表現力の高さ(No.4〜5)から、今回は API Blueprint を採用しました。

以下では、上記の評価軸を順に触れながら、API Blueprint の使い方をご紹介します。

API Blueprint の詳細

言語仕様

API Blueprint は、API ドキュメントを書くために、Markdown を拡張して作られた仕様です。ファイルの拡張子名は apib です。

API 開発プラットフォームを提供する Apiary2017年1月に Oracle が買収したスタートアップ)が主に推進していますが、仕様自体はオープンで、後述する aglio などのツールが多数開発されています。

言語仕様については、大まかに知りたい場合は API Blueprint Tutorial を読むのが良いと思います。

具体例は、公式サイトからリンクされた Examples もありますが、これは apib ファイルしかないので、どういう HTML が生成されるのかイメージしづらいです。

aglio のページで、API Blueprint 形式のファイルと、それに対応した HTML が公開されているので、以下の2つのファイルを見比べてみるのが、最初はわかりやすいと思います。

私の方でも、もう少し短くてシンプルな例を書いてみたので、こちらもご参考ください。

API Blueprint の詳細な仕様は API Blueprint Specification で公開されています。API Blueprint にある程度慣れたら、この仕様をざっと読んで、どういう表現ができるのか押さえておくことをオススメします。私はこれを読んで、enum の使い方やデフォルト値の指定方法などを知りました。

ところで、API Blueprint のなかでは、以下のようにリクエストやレスポンスのデータ形式を定義できます。

これは Markdown Syntax for Object Notation (MSON) という、Markdown で JSON および JSON Schema を表現するための仕様です。API Blueprint の内部で JSON を表現するために Apiary が開発した、とのことです。

編集方法・文法ミスの訂正

apib ファイルを編集する際には、aglio という API ドキュメント生成ツールを使います。

aglio は、apib ファイルから単一の HTML ファイルを生成するツールです。また、aglio には HTTP サーバとしての機能もあり、apib ファイルの更新を自動検知して、その HTTP サーバ上のページを自動的にリロードする機能を持ちます。

まず、以下のように –server オプションを付けて aglio を起動します。

http://127.0.0.1:3000/ にアクセスすると、以下のようなページが表示されます。

この状態で、好きなエディタで sample.apib を編集して保存すると、編集を自動検出してレンダリングが再実行され、http://127.0.0.1:3000/ 上のページが更新されます。Web ベースのエディタを強制されないというのは、個人的には嬉しいポイントでした。

なお、保存した apib ファイルに文法ミスがあると、以下のように、該当箇所を強調表示して知らせてくれます。

ちなみに、HTML ファイルの生成にこだわらないのであれば、Apiary にアカウントを作って、Apiary の Editor を使うこともできます。Apiary Editor は、swagger-editor と同様に Web ブラウザ上で編集可能で、文法エラーをリアルタイムに表示してくれます。

HTML ファイルの生成

-o オプションを指定して aglio を実行すると、aglio で表示されものと同じページが、単一の HTML ファイルとして生成されます。CSS なども HTML ファイルに同梱されるため、配布するのはとても楽です。

公式サイトの Tools のページ にある Renderers を一通り試したのですが、aglio の HTML が最も洗練されているように見えました。

記載の柔軟性

API Blueprint では、仕様書全体の先頭や、各 API の説明の先頭に、Markdown 形式で自由に説明を書くことができます。

例えば、aglio のページにある例(HTML)の Overview の部分や、Notes API の先頭に書かれた “Important Info” の部分は、Markdown で書かれた説明です。リンク、引用、リスト、表などが自由に使えることがわかると思います。

ちなみに、aglio は aglio でしか使えない拡張表現(注意点をブロック表示する ::: warning など)にも対応しています。

Markdown で自由に説明を書けるのは便利ですが、それがどこにレンダリングされるかはツールに依存するので注意が必要です。例えば、aglio ではきれいに表示される説明が、Apiary では変な位置に表示される、ということがありました。

API Blueprint を使ってみて、つまづいた点

リクエスト/レスポンス例の書き方

MSON のデータ例にハイフン(-)が含まれていると、そのハイフンの前までしか読み込まれないという問題があります。例えば、以下のように書くと、url は http://techblog.gmo、publishedAt は 2017 までしか読み込まれません。

この問題は、以下のようにデータ例をバッククォートで囲むことで解決します。

API Blueprint Specification の説明では、データ例 (example value) はすべてバッククォートで囲まれているので、ハイフンの有無に関係なく、普段からそうしておくのが無難だと思います。

aglio の制限

API Blueprint では、リクエストの URL パラメータ、ボディ、およびレスポンスのボディを、MSON 形式で定義することができます。また、”Data Structures” という節で独自の型を定義し、その型をボディの定義などから参照することができます。

しかし、現在の aglio(執筆時のバージョンは 2.3.0)には、ボディ部の定義を表示できないという制限があります。また、Data Structures で定義された型については、JSON Schema にも表示されません。これらの定義は、ボディ部の例には反映されているので、書いた定義が無視されているわけではなさそうなのですが……。

aglio の GitHub にこの issue は登録されていて、開発は進んでいるようなので、今後のアップデートに期待したいと思います(参考:Rendering “attributes” section and Data Structures · Issue #103 · danielgtaylor/aglio)。

ちなみに、aglio 以外のレンダラも同様の問題があったのですが、Apiary のエディタではこれらの情報は問題なく表示されました。そのため、API Blueprint 自体の制限ではなさそうです。

まとめ

今回は、API Blueprint と aglio を使って、配布用の API 仕様書を自動生成する方法をご紹介しました。

  • Markdown を知っていれば、他の人が書いた例を参考になんとなく書ける
  • aglio だけインストールすれば、あとは好きなエディタを使ってすぐ書き始められる

という敷居の低さが、API Blueprint の魅力だと思います。

逆に、API 仕様書を先に作るのは嫌で、コードを先に書いてあとから API 仕様書を自動生成したい、という場合には Swagger のほうが良いと思います。そのあたりは、その時の要件に合わせて選択するのがよさそうです。この記事が、皆さんの今後の API 開発の参考になれば幸いです。

2015-11-26

Impala で unix_timestamp(now()) を実行しても現在時刻が返ってこない件について

impala-logo
(画像は Impala の Web サイト より転載)

GMOインターネット 次世代システム研究室 兼 GMOアドパートナーズ グループCTO室のM. Y.(自称DevOps担当)です。今回は、普段の業務で気付いた Impala の小ネタをご紹介します。小ネタではありますが、Impala 初見の人は結構つまづきやすいポイントだと思います。

出題編:Impala の TIMESTAMP 型の不思議な動作

最近、Impala に格納したデータを JDBC 経由で取得するコードを書いてみたところ、なぜか格納したつもりの時間よりも9時間前の時間が返される、ということがありました。

Impala には TIMESTAMP 型で格納されているデータを、java.sql.Timestamp クラスのオブジェクトとして取得しているのに、なんで時間がずれるんだろう? データを入れ間違えたのかな?と思って、impala-shell を使ってデータを調べていたら、さらに不思議な動作に気づきました。unix_timestamp(now()) が現在時刻のUNIXタイムスタンプを返していないようなのです。

Impala には now 関数があり、マニュアルの Built-in Function Support によると、これは現在時刻を TIMESTAMP 型の値として返す関数です。例えば、SELECT 文で実行すると、以下のような値を返します。以下は、11月20日の14時に実行した例です。これは確かに現在時刻を返しています。

そして、now() の返り値を unix_timestamp() に渡して、現在時刻のUNIXタイムスタンプを取得すると、以下のような結果が得られます。

この 1448029930 から日時を逆算すると、JST の 2015-11-20 23:32:10 (このSQLを実行した時刻の9時間後)になりました。本来は 1447997530 になるはずなのですが……? どういうこと?

Impala の TIMESTAMP は UTC との相対値として格納される

この異常事態にそれまでの調査はどうでもよくなってしまい、とりあえずImpalaのマニュアルに飛びつきました。Impalaのマニュアルには、TIMESTAMP 型の定義について、こう書かれています。

Time zones: Impala does not store timestamps using the local timezone to avoid undesired results from unexpected time zone issues. Timestamps are stored relative to UTC.

Impalaのマニュアル:TIMESTAMP Data Type

Impala は UTC との相対値として timestamp を格納する、とのこと。これを読んで「UNIXタイムスタンプとして格納するのではなくて、UTC??」と疑問を持つ人は多いと思います。私も思いました。ちなみに、このUTCとの相対値は、マニュアルのなかで “UTC timestamp” とも呼ばれています。「UTCタイムスタンプ」? タイムスタンプに、タイムゾーンがあるってどういうことなの……。

例えば、Impala の関係が深いデータベース製品である Hive にも TIMESTAMP 型がありますが、Hive のマニュアルには、以下のように「UNIXタイムスタンプを格納する」と明確に書かれています。これは納得できます。

Supports traditional UNIX timestamp with optional nanosecond precision.

Hiveのマニュアル:LanguageManual Types – Apache Hive – Apache Software Foundation

Impala は時刻の文字列表現を自動的に TIMESTAMP に変換する

また、Impalaのマニュアルには、もうひとつ気になることが書かれていました。

Conversions: Impala automatically converts STRING literals of the correct format into TIMESTAMP values. Timestamp values are accepted in the format YYYY-MM-DD HH:MM:SS.sssssssss, and can consist of just the date, or just the time, with or without the fractional second portion. For example, you can specify TIMESTAMP values such as ‘1966-07-30′, ’08:30:00’, or ‘1985-09-25 17:45:30.005’. You can cast an integer or floating-point value N to TIMESTAMP, producing a value that is N seconds past the start of the epoch date (January 1, 1970).

Impalaのマニュアル:TIMESTAMP Data Type

STRING が許容可能な時刻文字列であれば、Impala はそれを自動的に TIMESTAMP 型に変換する、とあります。この変換の様子を確認するために適当なテーブルを定義し、TIMESTAMP 型で定義された registration_date カラムに値を挿入してみます。

格納後のデータを SELECT 文で確認すると、正しそうに見えます。

しかし、その結果を unix_timestamp 関数に通すと、今度は9時間先のUNIXタイムスタンプ(1448029930 = 2015-11-20 23:32:10 JST)が返されてしまいます。うーん。

Impala では to_utc_timestamp 関数と from_utc_timestamp 関数が必須

結論としては、Impala に TIMESTAMP 型を読み書きする際は、ローカルタイムを UTC に変換するために、to_utc_timestamp 関数と from_utc_timestamp 関数を必ず挟まなければなりません。上で説明した member_data テーブルの例については、以下のようにto_utc_timestamp 関数を使うと、正しく時間を格納できます。

この値を取得すると、以下のように登録した時刻の9時間前のデータになってしまいます。しかし、これは時刻が「UTCタイムスタンプ」として格納されているためで、これが Impala で TIMESTAMP 型を使う場合の正しいやり方になります。

一方、時刻を取得する際は、from_utc_timestamp 関数に、データを利用する環境のローカルタイムを渡す必要があります。

今度は、意図した通りの時刻が取得できていますね。JDBC 経由でレコードを取得する際も、SQL で明示的に from_utc_timestamp 関数を呼びだす必要があります。さもないと、”2015-11-20 05:32:10.497165000″ をUNIXタイムスタンプに変換した値が返されてしまいます。これが、JDBC 経由で TIMESTAMP 型を取得した場合に、9時間前の時間が返されてしまった理由でした。

解決編:unix_timestamp(now()) の正しい呼び出し方

最初に挙げた now 関数の例については、やや状況が複雑なのですが、

  • now 関数はローカルタイムの現在時刻の文字列表現を取得する
  • unix_timestamp 関数は引数に UTC タイムスタンプを取るため、now 関数の値を UTC の文字列表現として解釈する
  • このとき now 関数の返り値(JST = UTC + 9時間)を UTC として解釈するために、結果的に9時間進む

と考えれば理解できます。従って、以下のような SQL を実行すれば、現在時刻のUNIXタイムスタンプを正しく取得できます。

個人的には、「ImpalaのTIMESTAMP型 = UTCとの相対値 = UTCにおけるその時刻の文字列表現」というモデルを頭のなかに持っておけば、TIMESTAMP 型に関する Impala の動作について予想しやすくなる気がします。

最新のImpalaでの対応状況

この unix_timestamp 関数の動作がわかりにくいと思う人は他にも居るようで、Impala の JIRA にいくつかの issue が作られていました。先頭の issue が、今回の話に特に関係しています。

これらの issue について対策が行われて、Impala 2.2 からは impalad の起動時に -use_local_tz_for_unix_timestamp_conversions を指定することで、unix_timestamp 関数の挙動を Hive と同様のものに変更できるようになりました。つまり、unix_timestamp(now()) で現在時刻のUNIXタイムスタンプが返されるようになります。

ただし、Impala 2.2でも、use_local_tz_for_unix_timestamp_conversions はデフォルトで無効です。また、このオプションを有効にしても、Impala の TIMESTAMP 型は UTC タイムスタンプのままなので、to_utc_timestamp 関数および from_utc_timestamp 関数を使い続ける必要があります。今後も、Impala で TIMESTAMP 型を使う際には、まだまだおっかなびっくりになってしまいそうです。

最後に

余談ですが、Impala のマニュアルには、以下の一文が書かれています。

Impala does not store or interpret timestamps using the local timezone, to avoid undesired results from unexpected time zone issues. Timestamps are stored and interpreted relative to UTC. This difference can produce different results for some calls to similarly named date/time functions between Impala and Hive. See Impala Date and Time Functions for details about the Impala functions.

Impalaのマニュアル:SQL Differences Between Impala and Hive

タイムゾーンにまつわる予期せぬ問題による、予期せぬ結果を避けるために(”to avoid undesired results from unexpected time zone issues”)このような設計にした、とあるのですが、余計に問題をややこしくしているように感じるのは私だけでしょうか……。

最近 Cloudera からリリースされた、HDFS とは異なる特性を持つストレージの Kudu も、SQLクエリエンジンとして Impala をサポートしています。そのため、今後も Impala を利用するユーザは徐々に増えていくと思います。そのような方が Impala を触ってみて、TIMESTAMP 型の動作に頭を抱えたときに、この記事が参考になれば幸いです。