aglio/api-mockで表現できたこと・できなかったこと

※[aglioのコード](https://github.com/danielgtaylor/aglio)・[MSON仕様](https://github.com/apiaryio/mson)の細部は未読です。
「こうすればできる」等アドバイスありましたら幸いです。
[drakov](https://github.com/Aconex/drakov)の調査結果も追記しました。

---

## 概要

最近、Web APIの仕様書を[API Blueprint](https://github.com/apiaryio/api-blueprint)形式で作成し始めた。

API Blueprintの仕様（Markdown/[MSON](https://github.com/apiaryio/mson)）に沿って書かれたAPIは、npm packageの[aglio](https://github.com/danielgtaylor/aglio)でドキュメント化したり、drakovや[api-mock](https://github.com/localmed/api-mock)でモック化までできる。

間違いなく便利なのだが、実際に使ってみるとこちらが思うように表現できなかった点もあったのでメモ。

## 表現できたこと
### aglio

API Blueprintは[こちら](https://raw.githubusercontent.com/darai0512/APIBlueprintSampleOnNodeV4/gh-pages/README.md)
出力したaglio製API仕様書は[こちら](https://darai0512.github.io/APIBlueprintSampleOnNodeV4/)

### api-mock

aglio通りのmockが返る。仕様書とモックの整合性こそが最大のメリットだろう。

```
$curl http://localhost:3000/v1/client
Cannot GET /v1/client
$curl -X POST http://localhost:3000/v1/client
{
  "status": {
    "code": 200,
    "message": null,
    "details": []
  },
  "results": {
    "id": "5",
    "name": "darai0512",
    "type": "A"
  }
}$curl -X DELETE http://localhost:3000/v1/item/5/100
{
  "status": {
    "code": 200,
    "message": null,
    "details": []
  },
  "results": []
}$curl http://localhost:3000/v1/item/5/
Cannot GET /v1/item/5/
```

## 表現できなかったこと
### aglio

1. arrayの中に構造を持つ場合、schema(メタデータ)が表示されない
  - 配列内が同じ構造のオブジェクトの繰り返しの場合、メタデータ情報が反映されないのは辛い。自分のサンプルでは「カラム説明」を記載 <= 苦肉の策(^ ^;)
1. RequestのAttribute内のobjectのkey名にアンダーバーを使うと表記がバグる
  - Node製APIはキャメルケースが多いから？スネークケースが使えないのは辛い
1. Method(HTTP Verb)毎にしか色分けされない
  - RESTful APIを重視しているが、function的なAPI(GET or POSTのみ)も業務では多いはず
  - CRUDは単純過ぎて、それだけ用意しても使い勝手が悪い気がする
      - この値が欲しければ複数のAPIを順番通りに叩いて下さい、とか
      - 外部キー制約があるのでDELETEはこの順番で叩いて、とか
1. ParametersやRequestにおいて、enum型以外(string/number)のdefaultが設定できない/schemaに反映されない

### api-mock

1. 複数のAPI Blueprintファイル(以後「apibファイル」と表記)を連結してのmock化ができない
  - apibファイル毎に多プロセスで実行してもいいが、portが異なるので不便
  - aglioなら`<!-- include(docs/sample.md) -->`と記述したapibファイルを用意すれば連結できたが、api-mockでこのファイルを実行してもincludeされない
  - drakovで実現できると思いきや、現状できない様子(Appendix3参照)
  - [シェルスクリプトで連結](http://dev.classmethod.jp/server-side/api-document-with-api-blueprint/) or gulp task化(後述)
1. 一つのリソースパスに、一つのレスポンスしか返せない
  - aglioではHTTP Status毎のResponseを表現できたが、それを活かす術がない
1. リソースパス内に変数を使った場合、aglioでは変数を空にし別リソースパスのように表現できるが、api-mockでは変数を空にしてアクセスはできない
  - 同じリソースパス内の変数は全てrequired。[サンプル](https://darai0512.github.io/APIBlueprintSampleOnNodeV4/)の`### 商品一覧取得`の書き方、リソースパス`/v1/item/{groupId}/{id}`に対し変数idを空にし`/v1/item/{groupId}/`とするのは非推奨。リソースパス自体を分ける。
  - ちなみにaglioで同じリソースパス（## 見出し2）内に同actionを複数書いたら、Warningとなったが出力はされた
1. 利用可能なAPIのリソースパスがわかりにくい
  - dracovでは起動時に利用可能なAPIの全リソースパスを表示してくれる(Appendix3参照)
1. node v6で入らない
  - centos/macで、v6.3.1,v6.4.0においてprotagonist@1.2.6のinstallでErrorが起き、導入できなかった
  - dracovとaglioはv6でも使える

## gulp-aglio

[gulp-aglio](https://github.com/nnnnathann/gulp-aglio)を使えば、aglioをgulp task化できる

```
$npm install gulp-aglio --save-dev
gulp-aglio@0.0.10
$cat gulpfile.js
var gulp = require('gulp');
var aglio = require('gulp-aglio');

gulp.task('aglio', function() {
  gulp.src('doc/*.apib')
    .pipe(aglio({template: 'default'})) // ここでoption指定 
    .on("error", function (err) { console.log("Error : " + err.message); })
    .pipe(gulp.dest('./'))
});
gulp.task('watch', function() {
  gulp.watch(["doc/*.apib"], ['aglio']);
});
gulp.task('default', ['aglio', 'watch']);
```

基本的にaglioのオプションが使えるようだが、ほとんどが未確認

- メリット
  - ドキュメントの編集と出力を一度にできる
  - 複数の.apibファイルをまとめ上げられる(gulp taskを自分で追加)
- デメリット
  - protaganistとstreams2に依存するためnode v0.10.xでないと動かない
      - gcc verをあげれば（Appendix1参照）node4とかでもできます！

## Appendix1. 導入方法

```
$node --version
v4.4.7
# 4系ではaglio導入にgcc v4.8.X以上が必要(centosでは要確認)
$gcc -v
gcc version 4.8.2 20140120 (Red Hat 4.8.2-15) (GCC)
$npm install aglio api-mock --save-dev
aglio@2.2.1 
api-mock@0.3.2
```

※gccは、自分は[こちら](http://www.task-notes.com/entry/20151114/1447492231)を参考にsudoで導入した

## Appendix2. 使い方

~~[package.jsonのscripts](https://github.com/darai0512/APIBlueprintSampleOnNodeV4/blob/master/package.json#L4-L8)に下記を記載して利用~~
->package.jsonのscripts中のコマンドは、global installしていない場合、自動的にlocalパスのコマンドを読んでくれると最近知った。
なのでpackage.jsonのscripts内においては、下記"./node_modules/.bin/"は不要。

```
# localhostでドキュメントを表示しながらAPI Blueprintを編集
$./node_modules/.bin/aglio -i docs/sample.md -s
# ドキュメント出力
$./node_modules/.bin/aglio -i docs/sample.md -o index.html
# mock (-pでポート番号指定、デフォルトは3000)
$./node_modules/.bin/api-mock -p 3005 docs/sample.md
```

API Blueprint自体の書き方は、下記サイトがわかりやすかった
http://blog.amedama.jp/entry/2016/03/08/202403

## Appendix3. drakov

```
# node v6でも導入可能
$npm install drakov --save-dev
drakov@1.0.0
$npm run
Scripts available in APIBlueprintSampleOnNodeV4 via `npm run-script`:
  drakov
    drakov -f doc/*.apib --public -s ./index.html
  drakov-watch
    npm run drakov -- --watch # 初回の変更分しかwatchしてくれないorz
```

- drakovは起動時に、mock化した各APIのリソースパスを出してくれる
- sオプション(silent)は、fオプションで指定したapi serverの他に、document serverを起動してくれる。aglioで吐き出したhtmlを公開できる。
  - 自分はにgithub pagesで十分
- **複数のAPI Blueprintファイルを指定できる。**
  - **が、複数指定してもアルファベット順で先頭の1ファイル分しかmock化できないorz**
  - 課題だったファイル分割ができない問題を補ってはくれなかった。

### npm run arguments

dracov-watchのように、package.jsonのscripts中でnpm runを呼び出して元コマンドの引数を追加したい場合は、`npm run command -- -(args)`とする。

PHPのcomposerも、npmの影響を受けて同様の仕組みを採用していると先輩が教えてくれました！