サーバーレスアーキテクチャをReduxで


Redux でサーバーレスアーキテクチャを実装しやすくするパッケージ [redux-lambda](https://github.com/nak2k/node-redux-lambda) を作成したので宣伝を兼ねた使い方の説明記事です。

（この記事は[React.js Advent Calendar 2015](http://qiita.com/advent-calendar/2015/reactjs)の14日目の記事です）

## 前提

サーバーレスアーキテクチャについては以下を参考に。

- [サーバーレスアーキテクチャという技術分野についての簡単な調査 - Qiita](http://qiita.com/zerobase/items/3bc0d15980b472af841d)

サーバーレスアーキテクチャで使う [AWS Lambda](http://aws.amazon.com/jp/lambda/) 自体については使い方を理解しているものとします。

## 概要

Redux の Middleware はアプリケーションの中で最終的には「Actionを受け取って次の処理にActionを渡す関数のチェーン」として構成されます。

今回開発した redux-lambda は上記の関数チェーンを AWS Lambda 上の関数につなげる Redux Middleware と、Lambda 関数を Redux Middleware と同じ形式で実装できるようにするためのユーティリティをまとめたパッケージになります。

redux-lambda を使った全体の構成を図で示すと以下の通りです。

![redux-lambda.png](https://qiita-image-store.s3.amazonaws.com/0/27053/bcd19431-f02a-c8cb-bcbf-bea623702bd8.png)

## サンプル

[redux-lambda](https://github.com/nak2k/node-redux-lambda) の git リポジトリ内の [example](https://github.com/nak2k/node-redux-lambda/tree/master/example) フォルダにサンプルがあります。

## クライアント側の実装

### ブラウザー用 AWS SDK の準備

ブラウザー上のアプリケーションから AWS Lambda を直接呼び出すためにブラウザー用の AWS SDK を準備します。

以下の SDK Builder のページから最低限 AWS.Lambda を組み込んだ SDK を作成してダウンロードします。

- [Build your own AWS SDK for JavaScript](https://sdk.amazonaws.com/builder/js/)

### AWS SDK の初期化

ダウンロードした AWS SDK を組み込んで、初期化処理を実装します。

AWS SDK の初期化は AWS IAM の [Web Identity Federation](http://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_providers_oidc.html) 機能を使うか [Amazon Cognito](http://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_providers_oidc_cognito.html) を使って初期化します。

いずれの方法で初期化する場合も AWS Lambda に対して `lambda:InvokeFunction` のアクションが許可されている IAM Role での初期化が必要です。

以下、Facebook を IdP として Web Identity Federation で AWS SDK を初期化する例です。

```js
AWS.config.credentials = new AWS.WebIdentityCredentials({
  RoleArn: process.env.AWS_LAMBDA_INVOKER_ROLE_ARN,
  ProviderId: 'graph.facebook.com',
  WebIdentityToken: authResponse.accessToken
});

AWS.config.update({ region: process.env.AWS_REGION });
```

### redux-lambda の組み込み

Redux アプリケーション開発時に `configureStore` を実装する際に redux-lambda が提供する Middleware を適用します。

```js
import { applyMiddleware, createStore } from 'redux';
import { lambda } from 'redux-lambda';
import rootReducer from '../reducers';

const finalCreateStore = applyMiddleware(
  lambda('LambdaExample')
)(createStore);

export default finalCreateStore.bind(null, rootReducer);
```

`lambda` Middleware の引数に呼び出す Lambda 関数の名前を指定します。

### Lambda を呼び出す Action Creator の作成

Lambda 側で処理したい Action を区別するために Symbol プロパティ `LAMBDA` を指定した Action を生成する Action Creator を作成します。

```js
import { LAMBDA } from 'redux-lambda';

export const PING = 'PING';

export const ping = () => ({
  \[LAMBDA]: true,
  type: PING,
});
```

 *（LAMBDA プロパティ指定行の先頭の \ は削除で）*

ここで Action Creator が返した Action が、ほぼそのまま Lambda 関数呼び出し時の Payload として渡されます。

### Lambda の結果を処理する Reducer の作成

Lambda 関数呼び出し結果も Action として Reducer に渡されることになるので対応する Reducer を作成します。

```js
const rootReducer = (state = initialState, action) => {
  switch (action.type) {
  case PONG:
    return Object.assign([], state, {
      result: action,
    });
  }
  return state;
};
```

`action.type` に Lambda 関数側で設定したアクション名が入っているかどうかで Lambda 呼び出し結果であることを判定します。

## サーバー側の実装

### サーバー Middleware の作成

クライアント側の `lambda` Middleware から渡された Action を処理するサーバー側の Middleware を作成します。

```js
const exampleMiddleware = store => next => action => {
  next({
    type: 'PONG',
    date: new Date(),
  });
};
```

基本的に Redux Middleware と同じ形式で作成できます。

`action` にクライアント側の Action Creator が作成した Action が渡され、`next` に次の Middleware に Action を渡すための関数が渡されます。`store` については後述します。

`next` には Middleware の処理結果の Action を渡す必要があります。`next` に渡した Action は最終的には Lambda 関数の実行結果として返された後に、クライアント側の Middleware チェーン内で次の Middleware に dispatch されます。

### Middleware の Lambda 関数化

作成した Middleware に redux-lambda で提供している `applyMiddleware` を適用して Lambda 関数化した結果を export します。

```js
const { applyMiddleware } = require('redux-lambda/server');

const store = {
};

export const handler = applyMiddleware(
  exampleMiddleware
)(store);
```

`store` については現在のところ特に内容を定めておらず Middleware 内で使用したい任意のオブジェクトを渡せるようになっています。

Lambda の実行コンテキストは再利用されるので、DBへの接続など一度初期化すれば再利用可能なオブジェクトを渡すのに使うと良さそうです。

### Lambda 関数の登録

現時点（2015年12月）では AWS Lambda 上の node.js が古いため、babel を適用した上で Lambda 関数として登録します。

サンプルでは [gulp lambda](https://github.com/nak2k/node-redux-lambda/blob/master/example/tasks/lambda.js) タスクで登録していますが、詳細については割愛します。

## その他

REST API が必要な場合は作成した Lambda 関数を別途 [API Gateway](https://aws.amazon.com/jp/api-gateway/) と組み合わせれば良いかと。
Redux アプリケーションから呼ぶ分には API Gateway を経由してもコストが増えるだけに思えるので割り切った作りにしています。