Go
js
api

API Docs in コネヒト〜2017年、冬〜

はじめに

本記事はコネヒト Advent Calendar 2017の21日目のエントリーになります。

こんにちは!本日は @itosho のターンです。2回目の登場でございます。本当はアイドルか野球の話をしたいのですが、今日はAPIドキュメントの話をさせていただきます。

TL;DR

  • コネヒトではAPIドキュメントをGithubで管理してる!
  • でも場合によってはAPIDOCが便利そう!
  • 試してみた!よさげ!
  • 目的に応じてツールを選んでいこう!

コネヒトでのAPIドキュメントの運用方法

現在、コネヒトではAPIドキュメントをGithubで管理しています。

具体的にはAPI Docs用のリポジトリを作成し…

  1. MarkdownでAPIの仕様を定義
  2. それをPull Request形式で仕様レビュー
  3. 一定数のLGTMをもらったらマージ〜実装

というフローでAPIドキュメントを運用しています。

Github管理のメリット / デメリット

メリットはたくさんあるのですが、特にいいなと思っているのは…

  1. ソースコードと同じフローでドキュメント作成〜管理出来る
    1. 慣れ親しんだやり方で非同期に仕様定義出来る
  2. 開発者同士のコミュニケーションコストが少ない
    1. ネイティブエンジニアとサーバーサイドエンジニア間の言った言わない問題がない
  3. 過去の経緯がGithubをみれば全部分かる
    1. 途中から入ったメンバーでも安心
  4. 常に信頼出来るドキュメントが1つだけ存在することの安心感
    1. api_docs_20171224.xls という名の闇ドキュメントが生まれない

あたりです。
ただ、もちろん、デメリットがないわけではありません。具体的には…

  1. モックサーバーを生成することが出来ない
  2. ソースコードからドキュメントを自動生成することが出来ない
  3. 実際のコードと分離しているので、軽微な修正でも2つのリポジトリに手を加える必要がある(分離していること自体はメリットでもあります)

といった課題があります。

コネヒトで解決したい課題

上記デメリットの中で、1.と2.に関しては、SwaggerJSON Schemeを利用したツールを導入すれば解決出来ると思うのですが、現時点ではGithub管理で十分にワークしているので、それほど課題に感じてはいません。(Swaggerあったら便利そう!使えたらかっこよさそう!だなとは思っています:innocent:

ただ、3.についてはたまに面倒くささを感じることがあり、具体的には、クライアントとサーバーサイドの実装者が異なる場合は、このやり方でいい感じにワークしているのですが、例えば、管理画面1のようなクライアントとサーバーサイドの実装者が同じ、もしくはサーバーサイドエンジニアだけで開発を行っているケースでは、このやり方を採用するとややオーバーヘッドがかかります。

それ、APIDOCで解決出来るのでは?

そこで今回、その課題を解決するために導入してみたのがAPIDOCです。
APIDOCは実際のソースコード上にAPIの仕様をコメント形式で記述し、それをHTMLドキュメントとして出力出来るツールです。

メルカリさんでも導入実績があり2、僕自身これは便利だなと思ったので、これからAPIDOCの使い方を簡単に紹介させていただきます。

インストールする

APIDOCはnpmパッケージとして配布されています。グローバルインストールしてもよいですが、今回はプロジェクト配下にインストールしたいと思います。

$ cd プロジェクト配下
$ yarn init # 適宜package.jsonの設定を行ってください
$ yarn add apidoc
$ yarn install

※yarnコマンドを利用していますが、npmコマンドでも問題ありません。

仕様を書く

では早速ドキュメントを書いていきたいと思います。
今回は架空のアイドルAPIを用いた単純なCRUD APIを例にしたいと思います。

また、サンプルではGoを利用していますが、他にもRuby, PHP, Python, Java, JavaScript, Elixirなど幅広い言語に対応しています。

例1.アイドルリスト取得API

GET /idols でアイドルのリストが取得出来るAPIの場合だとこんな感じになります。

handler.go
/**
 * @api {get} /idols/ 1.アイドルリスト取得API
 * @apiName GetAllIdols
 * @apiGroup Idol
 * @apiDescription 全アイドル情報のリストを返す。
 *
 * @apiSuccess {Object[]} idols アイドルのリスト情報
 * @apiSuccess {String} idols.id アイドルID
 * @apiSuccess {String} idols.name 名前
 * @apiSuccess {Number} idols.age 年齢
 * @apiSuccess {String} idols.profile プロフィール情報
 * @apiSuccess {String} idols.created_at 作成日時(ISO8601形式)
 * @apiSuccess {String} idols.updated_at 更新日時(ISO8601形式)
 * @apiSuccessExample {json} Success-Response:
 *     HTTP/1.1 200 OK
 *     [
 *       {
 *         "id": 1,
 *         "name": "松村沙友理",
 *         "age": 25,
 *         "profile": "乃木坂46のメンバーの一人。",
 *         "created_at": "2017-12-01T10:10:10Z",
 *         "updated_at": "2017-12-01T10:10:10Z"
 *       },
 *       {
 *         "id": 2,
 *         "name": "小林由依",
 *         "age": 18,
 *         "profile": "欅坂46のメンバーの一人。",
 *         "created_at": "2017-12-02T10:10:10Z",
 *         "updated_at": "2017-12-02T10:10:10Z"
 *       }
 *     ]
 */
func (i *Impl) GetAllIdols(w rest.ResponseWriter, r *rest.Request) {
    idols := []Idol{}
    i.DB.Find(&idols)
    w.WriteJson(&idols)
}

簡単に各パラメーターの説明をすると…

  • @api: エンドポイントを定義します。最初にリクエストメソッドを書いて、その後にパスを書きます。タイトルを書くことも出来ます。(このパラメーターだけ必須項目です)
  • @apiName: API名を書きます。命名規則は特にありませんが、ドキュメント化した時アンカー(ページ内リンク)になるので、関数名やエンドポイントなどから一意となるようにしておく必要があります。
  • @apiGroup: グループを定義します。ドキュメント化した時にここで定義した情報をもとにグルーピングしてくれるので、リソース単位でグループをつくるのがいいかなと思います。
  • @apiDescription: APIの説明文です。なくてもいいですが、注意すべきことがあれば書いておきましょう。
  • @apiSuccess: 成功時(status: 200)のレスポンスを定義します。最初に型を書いて、その後にフィールド名を書きます。フィールドの説明文を書くことも出来ます。
  • @apiSuccessExample: 具体的にどういうレスポンスが返ってくるかを書くことも出来ます。全部のAPIで書く必要はないと思いますが、あると親切ですね。

という感じなので、phpDocumentorやJavadocを書いたことがある人はとっつきやすいと思います。

例2.アイドル新規作成API

もうひとつ POST /idols でアイドルが作成出来るAPIもみてみましょう。

handler.go
/**
 * @api {post} /idols 3.アイドル新規作成API
 * @apiName CreateIdol
 * @apiGroup Idol
 * @apiDescription アイドルを新規に作成する。作成したアイドル情報を返す。
 *
 * @apiParam {String} [name] 名前
 * @apiParam {Number} [age=17] 年齢
 * @apiParam {String} [profile] プロフィール情報
 * @apiParamExample {json} Request-Example:
 *     {
 *       "name": "西野七瀬",
 *       "age": 23,
 *       "profile": "乃木坂46随一の人気を誇るメンバー。"
 *     }
 *
 * @apiUse IdolResource
 *
 * @apiError BadRequest リクエストパラメーターが正しくない場合
 */
func (i *Impl) PostIdol(w rest.ResponseWriter, r *rest.Request) {
    idol := Idol{}
    if err := r.DecodeJsonPayload(&idol); err != nil {
        rest.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    if err := i.DB.Save(&idol).Error; err != nil {
        rest.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    w.WriteJson(&idol)
}

新たに利用したパラメーターを簡単に説明させていただきます。

  • @apiParam: リクエストパラメーターを定義します。レスポンスと同様、最初に型を書いて、その後にパラメーター名を書きます。 [] で任意項目であることや =hoge でデフォルト値を表現出来ます。
  • @apiError: エラーレスポンスを定義します。@apiErrorExample と併せて用いると親切かなと思います。
  • @apiUse: こちらは事前に定義しておいた情報を利用したい時に使うパラメーターです。例えば、同じリソースを扱うAPIであれば、レスポンスは同じになることが多いと思います。その時、いつも同じ情報を書くのは手間なので、事前に @apiDefine として定義しておくことで、情報を使いまわすことが出来ます。今回の例ではアイドルリソースを返すレスポンスを以下にように定義しています。
handler.go
/**
 * @apiDefine IdolResource
 * @apiSuccess {String} id アイドルID
 * @apiSuccess {String} name 名前
 * @apiSuccess {Number} age 年齢
 * @apiSuccess {String} profile プロフィール情報
 * @apiSuccess {String} created_at 作成日時(ISO8601形式)
 * @apiSuccess {String} updated_at 更新日時(ISO8601形式)
 */

他にも様々なパラメーターがあるので、詳細は公式ドキュメントをご覧ください。

ドキュメント化する

では、コメントで定義したAPI仕様をドキュメント化したいと思います。
ドキュメント化する前に package.json 内に予め apidoc というフィールドを用意して、その中にドキュメントのメタ情報やURLを定義することが出来ます。

今回は以下のような package.json を用意しました。

package.json
{
  "name": "fake-idol-api",
  "version": "1.0.0",
  "repository": "ssh://git@github.com/itosho/fake-idol-api.git",
  "license": "MIT",
  "dependencies": {
    "apidoc": "^0.17.6"
  },
  "scripts": {
    "apidoc": "apidoc -f handler.go -o doc"
  },
  "apidoc": {
    "name": "Fake Idol API",
    "title": "Fake Idol API Docs",
    "description" : "Example API Docs usging APIDOC",
    "url" : "https://api.fake-idol.example.com/v1"
  }
}

package.json を整えたら、以下のコマンドを実行します。

$ yarn apidoc

すると doc ディレクトリ配下にHTMLファイルなどが出力されます。

今回は http://localhost:8080/doc/ でドキュメントが閲覧出来るようにしているので、実際にアクセスしてみると、以下のようなAPIドキュメントが表示されます!

image.png

今回作成したサンプルは下記のリポジトリにまとめていますので、興味がある方はご覧ください。
https://github.com/itosho/fake-idol-api

使ってみた感想

まだヘビーに使い込んだわけではないですが、個人的にはとてもシンプルで使いやすく、プロダクション利用にも耐えうるツールだと感じました。
特にクライアントとサーバーサイドの実装者が同じ場合、ドキュメント化するのをサボって、後々大変な目を見るというのはよくある話だと思うので、コストをかけずにさくっとAPIドキュメントをつくりたい時にはとても良い選択肢の一つになるのではないでしょうか。

おわりに

まとめ

何度か述べているように僕個人としては、サーバーサイドのコードにAPIドキュメントを混ぜるのはデメリットも多いと思っています。それでも、上述の通り、場合によっては有効だと思いますし、何よりドキュメントを書かないよりは遥かによいと思うので、状況に応じて、Github管理とAPIDOCを上手く併用していければと考えています。

APIの文書化にはSwaggerやHALなど便利なツールや仕様がたくさん存在します。もちろん、流行をキャッチアップすることも大切ですが、それよりも、そのツールを使うことで何を解決したいのかを考えることが一番大切なのではないでしょうか。
ですので、今後もコネヒトでは流行り廃りに流されず、ツールに溺れないように今の自分たちに必要なツールを積極的に取り入れていければいいなと思っています!

大事なのはツールではなく、動くコードです。

明日の予告

明日(22日目)のコネヒト Advent Calendar 2017はコネヒトが誇るインフラエンジニア @nagais のターンです!お楽しみに!

参考文献 / サイト

  • 『WEB+DB PRESS Vol.100』(技術評論社, 2017)
  • APIDOC

  1. コネヒトでは一部の管理画面をモノリシックな構成ではなく、API-クライアントの構成にしています。 

  2. 『WEB+DB PRESS Vol.100』の"メルカリ開発ノウハウ大公開"にAPIDOCを導入している旨の記述がありました。