Serverlessフレームワークの沼の話

# この記事の内容
Serverlessフレームワークでgcloud(Google Cloud Platform API)を使おうとしたらがっつり沼にハマったのでそのメモ。さっさと別の方法で解決したほうが良かった気もするけど、誰かの役に立てばと...

## 前提
AWS Lambdaのデプロイ管理ができるフレームワーク「[Serverless](https://github.com/serverless/serverless)」のv0.5系を使っている。Lambdaには通常、単一のjsファイルか、(node_modulesを含んだ)zipファイルをアップロードする。そのzipパッケージが50MBまでという制限上、Serverlessでは**browserifyしたものをアップロードしてサーバーサイド(Lambda)で動かす**という若干特殊なことをしている。

## 沼

ここからが沼の話。やりたかったのはLambdaからGoogle BigQueryを操作すること。そのため、[gcloud](https://github.com/GoogleCloudPlatform/gcloud-node)モジュールを使ってAPIを叩こうとした。すると下記のエラー。

```
Serverless: Error: Cannot find module './aes'
    at Function.Module._resolveFilename (module.js:339:15)
    at Function.Module._load (module.js:290:25)
    at Module.require (module.js:367:17)
    at require (internal/module.js:16:19)
```

まず疑ったのはbrowserifyの設定。今回の構成では、[serverless-optimizer-plugin](https://github.com/serverless/serverless-optimizer-plugin)というプラグインでデプロイ時にbrowserifyが走るようになっている。こいつの該当コードを見る。

```serverless-optimizer-plugin/index.js
      let b = browserify({
        basedir:          bundleBaseDir,
        entries:          [bundleEntryPt],
        standalone:       'lambda',
        extensions:       _this.config.extensions,
        browserField:     false,  // Setup for node app (copy logic of --node in bin/args.js)
        builtins:         false,
        commondir:        false,
        ignoreMissing:    true,  // Do not fail on missing optional dependencies
        detectGlobals:    true,  // Default for bare in cli is true, but we don't care if its slower
        insertGlobalVars: {      // Handle process https://github.com/substack/node-browserify/issues/1277
          //__filename: insertGlobals.lets.__filename,
          //__dirname: insertGlobals.lets.__dirname,
          process: function() {
          }
        }
      });
```

使ったこと無いオプションが並んでいたが、どうやらサーバーサイドでbrowserifyされたモジュールを使うオプションとしては正しそう。下記Issueが参考になった。

https://github.com/substack/node-browserify/issues/1540

serverless-optimizer-pluginでは、デプロイ時のみbrowserifyが実行され、ローカルでテストするときは通常通りCommonJSの依存関係解決が行われる。このままだとデバッグもし辛いので、同じ設定でbrowserifyを実行してみる。

```
$ browserify --ignore-missing --node -t babelify func/_handler.js -o func/handler.js
```

これで同じエラーが出る状態までもっていけた。

エラーが出ている場所は`forge`モジュール内部の次の箇所。動的に実行される`require()`はbrowserifyでは事前解決できない。よって依存モジュールがバンドルされず、実行時エラーになる。

``` forge/js/forge.js
var defineFunc = function(require, module) {
  module.exports = function(forge) {
    var mods = deps.map(function(dep) {
      return require(dep);
    });
```

次のIssueがまさにこの問題について。

https://github.com/digitalbazaar/forge/issues/215

どうやら解決する予定はあるらしい。でも予定は未定。詰んだ。

https://github.com/digitalbazaar/forge/issues/126

と思ってたら、forkされていい感じにしてくれているリポジトリを発見。ラッキー。

https://github.com/ysangkok/forge

これで前述のエラーが解消された！まあ次のエラーが出るんですけど。次は下記のモジュールでエラー。エラー内容メモするの忘れた...

https://github.com/stephenplusplus/hash-stream-validation

もう辛くなってきたけど、grepなり何なりでこいつに依存しているモジュールを突き止める。どうやらgcloudの中でも**storage API**だけらしい。今回使いたかったのは**Big Query API**なので、どうにかこいつだけimport出来ないか試してみた。

```通常.js
const gcloud = require('gcloud');
const bigquery = gcloud.bigquery({
  projectId: 'xxxxxxxxxxxx',
  credentials: {
    //...
  }
});
```

[gcloudのリポジトリ](https://github.com/GoogleCloudPlatform/gcloud-node/tree/master/lib)を見てみると、綺麗にディレクトリが分かれているので部分インポートが出来そうだった。しかし、やってみると**config_がない**と怒られる。無理やり感があるが、`https://github.com/GoogleCloudPlatform/gcloud-node/blob/master/lib/index.js`のコードを参考に、インポート文を次のように修正。

``` 修正後.js
const gcloud = {
  config_: {},
  bigquery: require('gcloud/lib/bigquery')
};

const bigquery = gcloud.bigquery({
  projectId: 'xxxxxxxxxxxx',
  credentials: {
    //...
  }
});
```

まだエラーでる。。。このモジュール。

```
Error: Failed to find available CRC-32C implementation.
```

どうやらこのモジュールでまた動的なrequire()を実行しているらしい。
https://github.com/ashi009/node-fast-crc32c

下記が問題のコード。

``` node-fast-crc32c/loader.js
var fs = require('fs');

module.exports = (function(loaders) {

var impls = [
  './impls/sse4_crc32c_hw',
  './impls/sse4_crc32c_sw',
  './impls/js_crc32c'
];

for (var i = 0; i < impls.length; i++) {
  try {
    var crc32 = require(impls[i]);
    if (crc32.calculate("The quick brown fox jumps over the lazy dog") == 0x22620404)
      return crc32;
  } catch(e) {
  }
}

throw new Error('Failed to find available CRC-32C implementation.');

})();
```

どうしようもないのでforkして修正。`./impls/sse4_crc32c_hw`を読み込んでしまうと、ネイティブモジュールを実行するのでどのみちbrowserfiyでアウト。`./impls/js_crc32c`のみを事前ロードして実行するように修正。

https://github.com/KeitaMoromizato/node-fast-crc32c

``` 修正後.js
var fs = require('fs');

module.exports = (function(loaders) {

var impls = [
  require('./impls/js_crc32c')
];

for (var i = 0; i < impls.length; i++) {
  try {
    var crc32 = impls[i];
    if (crc32.calculate("The quick brown fox jumps over the lazy dog") == 0x22620404)
      return crc32;
  } catch(e) {
  }
}

throw new Error('Failed to find available CRC-32C implementation.');

})();
```

ここまでで終わり。何とかデプロイして動くことが確認できた。

## まとめ
そもそも「バックエンドで生まれたモジュールをフロントで使うためのbrowserifyをバックエンドで使う」というのがイケてないのではと...最初は便利かもと思ったけど。この辺りがうまくまとまらないのであれば(zipデプロイできる方法がある)、Serverlessに一元化は難しいなと思った。

あと、今回npm v3を使ったんだけど、一部のケースではflatten installされない事が分かった。今回のように、既存でインストールされているモジュールと同名のモジュール(forkしたもの)をインストールした場合、別の2つのモジュールとして認識される。そのためflattenな構造が崩れるので、デバッグ時には注意が必要。