Claudia.jsで簡単サーバレスアーキテクチャー

サーバレスアーキテクチャと聞いて、どのようなアーキテクチャを思い浮かべるでしょうか。Martin Fowler.comのサーバレスアーキテクチャの解説記事ではいくつかのサーバレスアーキテクチャの実装例がリストアップされています。

ここでは、最もよく使われると思われるフロントエンドアプリのためのサーバレスアーキテクチャを考えてみます。

フロントエンドアプリのためのサーバレスアーキテクチャ

API Gateway、Lambdaを中心とした標準的な構成です。

  • フロントエンド(SPA/Native)
  • 認証認可API
    • 代表的なサービス: Facebook, Twitter, Google, Auth0
    • 役割: ユーザの認証認可、OAuth Access TokenやJWTの発行・更新・検証
  • API Gateway
    • 代表的なサービス: AWS API Gateway, Apigee
    • 役割: フロントエンドが利用するAPIの公開、APIの共通処理(認証認可)、FaaS連携
  • FaaS (Function as a Service)
    • 代表的なサービス: AWS Lambda
    • 役割: BaaS連携、業務ロジック実行
  • BaaS (Backend as a Service)
    • 代表的なサービス: AWS DynamoDB, S3

しかしながら、実際にAWS上で一から実装しようとするとAPI GatewayとLambdaの連携、認証認可の実装、Lambdaの管理、デプロイや更新処理など面倒極まりない作業に直面します。何らかの抽象化レイヤーやツール無しにAWS上でサーバレスアーキテクチャを実現するのは困難だと実感しています。

ここでは、Claudia.jsというNode.js製のフレームワークを用いてこれらの課題を解決し、フロントエンドアプリのためのサーバレスアーキテクチャーを簡単に実現してみます。

Claudia.jsとは

AWS上でサーバレスアーキテクチャを実現するためのフレームワークの一つです。同様のフレームワークとしてServerlessやApex等が知られています。

  • Apex: Lambdaにフォーカス。作者のTJがAPI Gatewayをの1st citizenで無いと言っており連携が煩雑。
  • Serverless: YamlAWSの設定を大量に記述するスタイル。抽象化度合いが低い。

Claudia.jsはこれらと比較した際に、API Gatewayや外部認証までをも含めたサーバレスアーキテクチャ全体を通して実装するのが簡単です。Node.jsでExpressを用いたAPIサーバを書いていくようなイメージで、設定ファイルを記述していくServerless等と比較して柔軟な処理が行えます。

準備

1. Claudia.jsをインストールします。

npm install -g claudia

2. プロジェクトを作成します。

mkdir [Proj Name] && cd [Proj Name]
npm init
npm install --save claudia-api-builder
npm install --save aws-sdk
npm install --save axios

実装

以下のようなフロントエンドアプリを実装してみます。エラー処理や詳細は割愛します。また、Hello World的なものはCloudia.jsのTutorialをみた方が手っ取り早いです。

  1. フロントエンドアプリがSNSの認証認可APIからOAuthのアクセストークンを取得する。(略)
  2. フロントエンドアプリがアクセストークンを付与してAWS API Gatewayにリクエストを投げる。(略)
  3. AWS API GatewaySNSの認証認可APIを用いてアクセストークンを検証する。
  4. AWS API GatewayがFaaSの関数を呼び出す。
  5. AWS Lambdaの関数がAWS DynamoDBのテーブルからデータを取得してフロントに返す。

1. カスタム認証の作成

1. カスタム認証用のLambda関数を作成します。

authorizer.jsを作成します。Access Token検証APIを呼び出して有効な場合にAPI実行権限を付与しています。

var axios = require('axios');
    
//API実行ポリシーの生成
var generatePolicy = function(authToken, methodArn, context) {
    var awsInfo = methodArn.split(':');
    var region = awsInfo[3];
    var accountId = awsInfo[4];
    
    var apiInfo = awsInfo[5].split('/');
    var restApiId = apiInfo[0];
    var stage = apiInfo[1];
    var method = apiInfo[2];
    
    //アクセスさせたいリソース
    var resource = 'arn:aws:execute-api:' + region + ':' + accountId + ':' + restApiId + '/' + stage  +'/' + method + '/*';
    
    return {
        'principalId': authToken.split('-')[0],
        'policyDocument': {
            'Version': '2012-10-17',
            'Statement': [{
                'Effect': 'Allow',
                'Action': ['execute-api:Invoke'],
                'Resource': [resource]
            }]
        }
    };
};
    
var url = "Access TokenのValidateを行うAPIのURL";
    
//Tokenの検証とポリシーの付与
exports.auth = function auth(event, context, cb) {
  axios.get(url, {
    params: {
      access_token: event.authorizationToken //HTTP headerのAuthorizationキーの値
    }
  }).then(function (res) {
    var is_valid = true|false; //Tokenの有効性を検証して結果を格納
    if (is_valid) {
       cb(null, generatePolicy(event.authorizationToken, event.methodArn));
    } else {
       cb("Invalid Access Token");
    }
  });
};

2. デプロイ

claudiaコマンドでカスタム認証用のLambda関数をAWSにデプロイします。

claudia create --name custom-authorizer --region us-east-1 --handler authorizer.auth --config claudia-auth.json
  • --name: AWS Lambda上の関数名
  • --region: AWSのリージョン名
  • --handler: カスタム認証の関数名
  • --config: Claudia.js用の設定ファイル名(自動作成されるので任意)
{
  "lambda": {
    "role": "custom-authorizer-executor",
    "name": "custom-authorizer",
    "region": "us-east-1"
  }
}

2. APIとLambda関数の作成

1. APIとLambda関数を作成します。

app.jsという名前にします。

var ApiBuilder = require('claudia-api-builder');
var api = new ApiBuilder();
    
var AWS = require('aws-sdk');
var dynamodb = new AWS.DynamoDB.DocumentClient({region: 'us-east-1'});
    
api.registerAuthorizer('apiGatewayAuthorizer', {
  lambdaName: 'custom-authorizer', //AWS Labmda上のカスタム認証の名前
  headerName: 'Authorization' //Tokenを格納するHTTP headerのKey名
});
    
api.get('/news', function (req) {
  return dynamodb.get({
    TableName:'news',
    Key:{ "id": req.headers.id }
  }).promise().then(function(res) {
    return res.Item;
  });
},{ 
  customAuthorizer: 'apiGatewayAuthorizer' 
});
    
module.exports = api;

2. デプロイ

claudiaコマンドでAWSにデプロイします。設定ファイルclaudia.jsonが自動で作られます。

claudia create --region us-east-1 --api-module app
{
  "lambda": {
    "role": "myproj-executor",
    "name": "myproj-proj",
    "region": "us-east-1"
  },
  "api": {
    "id": "[PID]",
    "module": "app",
    "url": "[エンドポイントのベースURL]"
  }
}

3. AWS上の設定

  1. AWS DynamoDBのテーブル作成

    newsテーブルを作成する

     aws dynamodb create-table --table-name news --attribute-definitions AttributeName=id,AttributeType=S --key-schema AttributeName=id,KeyType=HASH --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1
    

    idキーで検索できるようにレコードを追加。

     aws dynamodb put-item --table-name news --item '{"id":{"S":"id001"}, "content":{"S":"News content 001"}}'
     aws dynamodb put-item --table-name news --item '{"id":{"S":"id002"}, "content":{"S":"News content 002"}}'
         :
    
  2. AWS Lambdaのrole設定

    1. IAM > Roles > [作成したRole名]
    2. Attach Pollicy > AmazonDynamoDBFullAccess
      • 強すぎるので実際は適当なPolicyを作成

4. API実行

  1. PostmanなどでHTTPヘッダのAuthorizationキーにAccess Tokenを、idキーに存在するidを付与して投げます。

    Claudia.jsでAPIを作成した際にはCORSはデフォルトでONになっていますが制限することもできます。

5. アップデート

  1. スクリプトを修正します。
  2. claudiaコマンドを実行します。
claudia update --config claudia-auth.json
claudia update --config claudia.json

まとめ

Claudia.jsを利用すれば、外部の認証認可APIを用いたフロントエンドアプリのためのサーバレスアーキテクチャを簡単に実装できます。

以上