サーバレスなアーキテクチャでのLambda Functionの分割単位について考える

## お題

本稿ではこんなAPIをAPIGatewayとLambdaで作りたいというお題だとします。

* USERリソース
    * POST /users         
    * GET  /users/{uid}   
    * DELETE /users/{uid} 
* FRIENDリソース
    * POST /users/{uid}/friends
    * GET  /users/{uid}/friends/{fid}
    * DELETE /users/{uid}/friends/{fid}

## どの単位でLambdaファンクションを作るか

最初に悩んだポイントです。

* 全6個、それぞれを個別のLambda Functionにする?
    * Pros: Microservicesな考えで、お互い独立する
    * Cons: 流石に数が多すぎてLambda Functionの管理がつらい
    * Cons: 各Lambda Functionで共通のロジックどうすんの？
* 全部を1個のLambda Functionにする？
    * Pros: 管理するLambda Functionは１個で済む
        * 慣れた普通のアプリケーションのような作り方ができる
    * Cons: モノリシック
        * 影響範囲(Usersだけ修正したのにFriendsについても心配になる)
        * 割当リソース(メモリ量、タイムアウト時間)調整が１箇所に引きずられる

### リソース単位にしてみた

結局、中庸でリソース単位にLambdaファンクションを作るようにし、共通ロジックはCommonなるnpmモジュールにしててみました。（これが正解なのかはわかりません）

1. SamplaApp_User
2. SampleApp_Friends

という2つのLambdaファンクションと、Commonモジュールで構成してみます。

```shell-session:ディレクトリ構造
.
├── common
│   ├── index.js
│   ├── lib
│   └── package.json
├── functions
│   ├── friends
│   │   ├── index.js
│   │   └── package.json
│   └── users
│       ├── index.js
│       └── package.json
└── project.json
```

※ ここで、しれっと`project.json`などが出てきていますが、このディレクトリ構造は[apex](http://dev.classmethod.jp/cloud/aws/how-to-manage-aws-lambda-functions-with-apex/)の使用を前提としたものです。

## ルーティング

さて、リソース単位にLambdaファンクションにするということは、`SamplaApp_User`ファンクションは

* POST /users         
* GET  /users/{uid}   
* DELETE /users/{uid} 

この３つの挙動をひとつのLambdaファンクションが担うことになります。何らかの条件で処理を分岐せねばならないのでその方法を考えます。

Lambdaファンクションのハンドラのインターフェイスは

```js:ハンドラ
exports.handle = function(e, ctx, cb) {
};
```

こうですが、Lambdaファンクションに渡されるイベント変数`e`を

```js:e
{
  operation: "get",
  params: {
    p1: 2222,
    p2: "hogehoge"
  }
};
```

このようにして`e.operation`で処理を分岐することにしてみました。

```shell-session:ディレクトリ階層
functions/users/
├── index.js
├── operation
│   ├── create.js
│   ├── destroy.js
│   ├── get.js
│   └── index.js
└── package.json
```

```js:index.js
const co = require("co");
const operation = require("./operation");

exports.handle = function(e, ctx, cb) {
  console.log("processing event: %j", e);
  co(function*(){
    const fn = operation[e.operation];
    if(!fn || typeof(fn) !== "function")
      throw "unsupported operation";
    
    return yield fn(e.params);
  }).then(result => {
    cb(null, result);
  }).catch(err => {
    cb(err);
  });
};
```

```js:operation/index.js
module.exports = {
  "create": require("./create"),
  "get": require("./get"),
  "destroy": require("./destroy"),
};
```

```js:operation/create.js
module.exports = function*(params) {
  console.log("user creating... %j" ,params);
  return {id: 1111, name: "hogehoge"};
};
```

上記のサンプルコードは`co`,`ジェネレータファンクション`,`yield`というES6の文法を多用しています。読み慣れない方は
http://qiita.com/keitarou/items/79a038a29e1f8e39573b
こちらの記事などを参考にしてください。

## 共通処理

これで、`SamplaApp_Users`,`SamplaApp_Friends`それぞれについてはこの型で各operationを実装していけばいいのですが、実装を進めていくと共通処理が出てくることになると思います。

ここではnpmでローカルのディレクトリをnpmモジュールとして取り込む方法を使うことにしました。

> npm install \<folder\>:
>
> Install a package that is sitting in a folder on the filesystem.

```shell-session:ディレクトリ構造（再掲）
.
├── common
│   ├── index.js
│   ├── lib
│   └── package.json
├── functions
│   ├── friends
│   │   ├── index.js
│   │   └── package.json
│   └── users
│       ├── index.js
│       └── package.json
└── project.json
```

※ ここで、しれっと`project.json`などが出てきていますが、このディレクトリ構造は[apex](http://dev.classmethod.jp/cloud/aws/how-to-manage-aws-lambda-functions-with-apex/)の使用を前提としたものです。

users, friendsそれぞれのpackage.jsonの`dependencies`に`../../common`を参照するように書きます。

```json:friends,usersそれぞれのpackage.json
  "scripts": {
    "preinstall": "npm install ../../common"
  },
  "dependencies": {
    "co": "^4.6.0",
    "my-common": "../../common"   <---ここ
  }
```

これにより`npm install` で `my-common`というモジュールとして取り込まれます。
※preinstallを書いているのは、commonが更新された時に`npm install`してもモジュールが更新されないのでその回避策です。

さらに`apex deploy`するときに自動的にパッケージングされるように`project.json`に[hook](https://github.com/apex/apex/blob/master/hooks/hooks.go)を書きましょう。

```json:project.json
  "hooks" :{
    "build": "npm install"
  }
```

## まとめ

まとまっているのかわかりませんが、

* Lambdaファンクションをある程度の単位で切るための方法
* 各Lambdaファンクションに共通な処理の扱い方

を考えてみました。

Lambdaの考え方からすると`e.operation`で処理を分岐するのは邪道なのかもしれません。
また逆に、共通処理があるくらいならひとつのLambdaファンクションにまとめるべきなのかもしれません。

最近の[Serverless 1.0-beta](https://github.com/serverless/serverless/blob/master/README.md)を見ていると、後者の「全部一個にまとめちまえ」という方向に進んでいるようにも見えます。