Redux でサーバーレスアーキテクチャを実装しやすくするパッケージ redux-lambda を作成したので宣伝を兼ねた使い方の説明記事です。
(この記事はReact.js Advent Calendar 2015の14日目の記事です)
前提
サーバーレスアーキテクチャについては以下を参考に。
サーバーレスアーキテクチャで使う AWS Lambda 自体については使い方を理解しているものとします。
概要
Redux の Middleware はアプリケーションの中で最終的には「Actionを受け取って次の処理にActionを渡す関数のチェーン」として構成されます。
今回開発した redux-lambda は上記の関数チェーンを AWS Lambda 上の関数につなげる Redux Middleware と、Lambda 関数を Redux Middleware と同じ形式で実装できるようにするためのユーティリティをまとめたパッケージになります。
redux-lambda を使った全体の構成を図で示すと以下の通りです。
サンプル
redux-lambda の git リポジトリ内の example フォルダにサンプルがあります。
クライアント側の実装
ブラウザー用 AWS SDK の準備
ブラウザー上のアプリケーションから AWS Lambda を直接呼び出すためにブラウザー用の AWS SDK を準備します。
以下の SDK Builder のページから最低限 AWS.Lambda を組み込んだ SDK を作成してダウンロードします。
AWS SDK の初期化
ダウンロードした AWS SDK を組み込んで、初期化処理を実装します。
AWS SDK の初期化は AWS IAM の Web Identity Federation 機能を使うか Amazon Cognito を使って初期化します。
いずれの方法で初期化する場合も AWS Lambda に対して lambda:InvokeFunction
のアクションが許可されている IAM Role での初期化が必要です。
以下、Facebook を IdP として Web Identity Federation で AWS SDK を初期化する例です。
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 を適用します。
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 を作成します。
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 を作成します。
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 を作成します。
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 します。
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 タスクで登録していますが、詳細については割愛します。
その他
REST API が必要な場合は作成した Lambda 関数を別途 API Gateway と組み合わせれば良いかと。
Redux アプリケーションから呼ぶ分には API Gateway を経由してもコストが増えるだけに思えるので割り切った作りにしています。