LoginSignup
33
28

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-09-01

お題

本稿ではこんな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は1個で済む
      • 慣れた普通のアプリケーションのような作り方ができる
    • Cons: モノリシック
      • 影響範囲(Usersだけ修正したのにFriendsについても心配になる)
      • 割当リソース(メモリ量、タイムアウト時間)調整が1箇所に引きずられる

リソース単位にしてみた

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

  1. SamplaApp_User
  2. SampleApp_Friends

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

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

※ ここで、しれっとproject.jsonなどが出てきていますが、このディレクトリ構造はapexの使用を前提としたものです。

ルーティング

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

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

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

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

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

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

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

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

ディレクトリ階層
functions/users/
├── index.js
├── operation
│   ├── create.js
│   ├── destroy.js
│   ├── get.js
│   └── index.js
└── package.json
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);
  });
};
operation/index.js
module.exports = {
  "create": require("./create"),
  "get": require("./get"),
  "destroy": require("./destroy"),
};
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.

ディレクトリ構造(再掲)
.
├── common
│   ├── index.js
│   ├── lib
│   └── package.json
├── functions
│   ├── friends
│   │   ├── index.js
│   │   └── package.json
│   └── users
│       ├── index.js
│       └── package.json
└── project.json

※ ここで、しれっとproject.jsonなどが出てきていますが、このディレクトリ構造はapexの使用を前提としたものです。

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

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

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

さらにapex deployするときに自動的にパッケージングされるようにproject.jsonhookを書きましょう。

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

まとめ

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

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

を考えてみました。

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

最近のServerless 1.0-betaを見ていると、後者の「全部一個にまとめちまえ」という方向に進んでいるようにも見えます。

33
28
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
28