Node.jsのライブラリ開発で得られた知見

Node.jsでライブラリを作成して、npmで公開するまでやってみたので
その開発を通して得られた知見をまとめておきます。

作ったもの：[ykokw/niftycloud\-auth](https://github.com/ykokw/niftycloud-auth)
（**個人として作成したものなので、非公式なものです**）

## シグネチャを作成する力

niftycloud-authは、NIFTY Cloud APIをリクエストするときに必要なシグネチャ計算をするライブラリです。  
NIFTY Cloud APIの認証は、AWS互換のシグネチャ認証方式が利用されているので、
[aws/aws\-sdk\-js](https://github.com/aws/aws-sdk-js)を使えばAPIリクエスト出来たりもするのですが、
シグネチャ作成についてのドキュメントを読みながら実装すると、色々とつまずいた点があったので、
今回つまずいたことは、次回別の言語で同じようなことやろうとしたときに活かせるはずだと思っています。
（ドキュメントもフィードバックして分かりやすくしていきます）

## ES6のクラスとcoモジュール

[クラス \- JavaScript \| MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes) を使って実装してみました。
継承させたり、親クラスのメソッドを呼び出したり、基本的なことはできるようになったはず。。。

あと、自分は [tj/co](https://github.com/tj/co) を使って非同期処理を同期処理っぽく書いたりもしていましたが、クラスだと `this` を使ってプロパティとかメソッドの呼び出しを行っていたので、
 `co.call(this, function* (){})` を使って、クラスのプロパティとかメソッドを呼び出せるようにしました。

（参考：[can we capture \`this\` in out co\-ed generator function? ・ Issue \#274 ・ tj/co](https://github.com/tj/co/issues/274)）

## その他のES6の文法

以下も実践してみました。どこかでみたことある文法の名前を知って「あ、これ分割代入か」ってなったりしていました。

- var使わない-> let / constを使う
- アロー関数
- 分割代入
- メソッドのデフォルト引数

## Dockerを使った開発環境

自分はVagrant + Ansible を使って開発環境をWindowsでもMacでも同じようにしています。
（参考：[ykokw/vm\-setup](https://github.com/ykokw/vm-setup)）

それで作成されるVMには、エディタとgitとdockerしかインストールされていないのですが、
Node.jsのdocker imagesさえ用意しておけば、containerを作成してむりくり開発ができることに気づきました。

```
docker run --rm -it --entrypoint bash -v /root/workspace/niftycloud-auth/:/usr/local/niftycloud-auth node:latest
```

tmux立ち上げて1画面でエディタを開き、もう1画面で上記コマンドを実行して `npm test` とか動作確認するイメージです。

nvmとかもあったりで、Node.jsの環境構築するのもそんなに大変じゃないのですが、
containerに閉じておくとVM側を割とクリーンに保てていいなと感じています。

## 便利なライブラリ

niftycloud-authで使っていて便利だったライブラリ書いておきます。

- [superagent](https://www.npmjs.com/package/superagent) + [superagent\-proxy](https://www.npmjs.com/package/superagent-proxy)
    - HTTP通信するためのライブラリ + superagentでproxy設定が必要な場合のライブラリ

- [nock](https://www.npmjs.com/package/nock)
    - HTTP通信のモックをしてくれるツール

テストコードのbeforeでモックを用意しておき、
テスト内で普通にHTTPリクエストすると、用意していたレスポンスが返るものです。
URLとかリクエストパラメータの条件を色々つけられるので、使い勝手がよかったです。

```javascript
describe("with header parameter", ()=>{
  before(()=>{
    nock(endpoint)
      .matchHeader('content-type', 'application/json')
      .matchHeader('x-nifty-date', function(dateValue){
          if(dateValue !== "") {
            return true;
          } else {
            return false;
          }
        })
      .matchHeader('authorization', function(authorizationValue){
        if(authorizationValue !== "") {
          return true;
        } else {
          return false;
        }
      })
      .delete(path)
      .reply(200, expectResponse);
  });
  it("should send header parameter in delete method", (next)=>{
    v4.delete(path, region, serviceId, {
      header: {"Content-Type": "application/json"}
    }).then((res)=>{
      assert.deepEqual(res.body, expectResponse);
      next();
    });
  });
});
```

ただし、Readmeのドキュメント読んでると、リクエストヘッダーの指定方法が
reqheadersというパラメータ使って指定するように書かれているんですが、
↑のコードのようにmatchHeaderメソッドで指定しないとうまく動作してくれませんでした。

（参考：[reqheaders ignored ・ Issue \#748 ・ node\-nock/nock](https://github.com/node-nock/nock/issues/748)



- [joi](https://www.npmjs.com/package/joi)
    - バリデーションが簡単にできるライブラリ

スキーマを定義しておいて、

```javascript
const clientSchema = {
  method   : Joi.string().required().valid('get', 'post', 'put', 'delete'),
  urlString: Joi.string().required().uri({
    scheme: ['http', 'https']
  }),
  options  : Joi.object({
    header: Joi.object().optional(),
    query: Joi.object().optional(),
    body : Joi.object().optional(),
    cb     : Joi.func().optional()
  }).optional()
};
```

validateメソッド呼び出すだけです。
↑のスキーマ書いておくと、仕様が一目で分かるのでいいなーと思いました。

```javascript
validateReqParameters(params, schema = clientSchema) {
  return new Promise((resolve, reject)=>{
    const validateResult = Joi.validate(params, schema);
    if (validateResult.error) {
      const invalidErr = new this.InvalidParametersError(
        "Request parameters is invalid",
        validateResult.error.details
      );
      reject(invalidErr);
    } else {
      resolve(validateResult.value);
    }
  });
}
```

- [power\-assert](https://www.npmjs.com/package/power-assert)
    - テストが失敗したときのメッセージをわざわざ書かなくて済むのはテスト駆動開発しているとありがたいです


## 「誰かが使うかも」と意識したインターフェース

v0.0.1からv0.1.0へのアップグレード中に、
このライブラリを使う人がいるかも、と思ってインターフェースを整理していました。

### メソッド定義

メソッドの引数には、指定が必須なものとそうじゃないものがあったので、
optionalなものを全部1オブジェクトにして省略できるようにしています。

（参考：[JavaScriptのメソッド定義する時に引数どうしようか悩んだ話 \- Qiita](http://qiita.com/ykokw/items/58de3acd5515f777cc17)

### PromiseとCallback

Promiseだけでもいいかなと思ったのですが、
[NIFTYCloud\-mbaas/ncmb\_js](https://github.com/NIFTYCloud-mbaas/ncmb_js)を参考に、PromiseでもCallbackでも使えるようにしました。

## CIとか

- [Travis CI \- Test and Deploy Your Code with Confidence](https://travis-ci.org/)
- [Coveralls \- Test Coverage History & Statistics](https://coveralls.io/)

Coveralls使い始めるのがすごく簡単でした。
すでに[istanbul](https://www.npmjs.com/package/istanbul)を使ってカバレッジ計測まではできていたので、

- `package.json` にcoverallsを追加
- `package.json` にcoverageタスクを追加
- Coverallsのコンソールでリポジトリ追加

だけでバッジが手に入りました。


> package.jsonにcoverageタスクを追加

は、ローカルでnpm testするたびにcoverallsがうごいてほしくなかったので、タスクを分けました。
↓ `package.json` のタスク部分

```
  "scripts": {
      "test": "istanbul cover _mocha --report lcovonly -R test/*",
      "coverage": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
  },
```

`.travis.yml` の `after_success` で coverageタスクを実行するように指定しています。
（npm testはTravis CI デフォルトのコマンドなので何も指定していないです。）
↓ `.travis.yml` の全部

```
language: node_js
node_js:
  - 6
after_success: npm run coverage
```


## テスト駆動開発とexamples

テストコードから書いて、テストが通るようにして、リファクタする、という
いわゆるテスト駆動開発をしているのですが、自分にとってテスト駆動開発は
ライブラリの仕様をコード化するための作業だったりして、全然ライブラリの品質に貢献できていない気がしました。
テスト手法の知識が足りてなかったりするのが原因なので、もっと勉強していきます。

あと、上と関連してユニットテストだけでは絶対うまくいっていないと考えていたので、
examplesフォルダに使い方が分かるサンプルコードを用意しておくていで、
実際のAPIキー、エンドポイントを設定して動作確認していたりしました。（そしてやっぱりそこで不具合が見つかったりしました。）

## さいごに

作ってみたライブラリは大したことないしターゲットがニッチなものですが、得るものは多かった気がします。
自分の技術の幅がちょっと広がったという意味で、作って良かったなという気持ちです。