見出し画像

今から始めるServerless Frameworkでサーバーレスデビュー【ファンクション番外編#4】

番外編ではファンクション編となります。
この編ではLambda関数の記述について説明します。
functionsディレクトリの構成は以下の通りです。

├── functions
│   ├── cognito.ts
│   └── hello.ts
│   └── user.ts

serverless.ymlのfunctionsセクションの内容は以下のようになっています。

# ------------------------------------------
# functions
# ラムダで使用する関数を設定
# ------------------------------------------
functions:

# 初期ファンクション
hello:
  handler: functions/hello.handler
  memorySize: 128
  timeout: 10
  events:
    - http:
        path: /hello
        method: get
        integration: lambda
        cors: true
        authorizer:
          name: ApiGatewayAuthorizer
          type: COGNITO_USER_POOLS
          authorizerId: !Ref ApiGatewayAuthorizer

# ユーザー作成認証する処理
customMessages:
  handler: functions/cognito.handler
  memorySize: 128
  timeout: 10
  events:
    - cognitoUserPool:
        pool: ${self:service}-user-pool-${opt:stage, self:provider.stage}
        trigger: CustomMessage
        existing: true
        
# ユーザー作成時にDBに情報を格納する処理
postConfirmation:
  handler: functions/user.handler
  memorySize: 128
  timeout: 10
  events:
    - cognitoUserPool:
        pool: ${self:service}-user-pool-${opt:stage, self:provider.stage}
        trigger: PostConfirmation
        existing: true

eventsには通常のGETやPOSTなどのリクエストを設定することができます。
これにより、API GatewayのAPIとして機能させることができます。
また、eventsにCognitoを指定することで、Cognitoのフローの中でトリガーを起点に処理を行うことができます。
Cognitoを使用する場合、既存のユーザープールにアタッチする場合は、existingをtrueに設定しないと新たにCognitoユーザープールが作成されてしまうので注意が必要です。

以下のようにすることで、Lambda Functionであるcognito.tsを以下のように設定すれば、Cognitoのトリガーで処理を挟むことができるfunctionsとなります。

// fucntions/cognito.ts

import { CognitoUserPoolTriggerEvent, Context, Callback } from 'aws-lambda';

// ------------------------------------------
// ユーザー作成認証する処理
// ------------------------------------------
export const handler = (event: CognitoUserPoolTriggerEvent, context: Context, callback: Callback) => {

/**
 * 新規登録のとき
 */
if (event.triggerSource === 'CustomMessage_SignUp') {
  event.response.smsMessage = `あなたのサービス確認コードは${event.request.codeParameter}です`
  event.response.emailSubject = '新規登録の確認コード'
  event.response.emailMessage = `下記URLからユーザー登録を完了してください。\<br\> URL: http://localhost:3000/signup/confirm?email=${encodeURIComponent(event.request.userAttributes.email)}&username=${event.userName}&code=${event.request.codeParameter} \<br\> Code: ${event.request.codeParameter}`
  
/**
 * 認証コード再送信
 */
} else if(event.triggerSource === 'CustomMessage_ResendCode'){
  event.response.smsMessage = `あなたのサービス確認コードは${event.request.codeParameter}です`
  event.response.emailSubject = '【再送信】新規登録の確認コード'
  event.response.emailMessage = `下記URLからユーザー登録を完了してください。\<br\> URL: http://localhost:3000/signup/confirm?email=${encodeURIComponent(event.request.userAttributes.email)}&username=${event.userName}&code=${event.request.codeParameter} \<br\> Code: ${event.request.codeParameter}`

/**
 * パスワードを忘れたとき
 */
} else if(event.triggerSource === 'CustomMessage_ForgotPassword') {
  event.response.smsMessage = `あなたのサービス確認コードは${event.request.codeParameter}です`
  event.response.emailSubject = 'パスワードリセットの確認コード'
  event.response.emailMessage = `下記URLからパスワード再登録を行ってください。\<br\> URL: http://localhost:3000/signup?email=${encodeURIComponent(event.request.userAttributes.email)}&username=${event.userName}&code=${event.request.codeParameter} \<br\> Code: ${event.request.codeParameter}`
}
callback(null, event)
}

Lambdaのfunctionsでは上記のように、各関数に対して個別の設定を行うことができます。
eventsを使用してAPI Gatewayとの結びつきやCognitoとのトリガーを設定することで、様々な動作や挙動を実現することができます。

①Cognitoのtriggerでの活用。
②REST APIとして活用。
③LambdaからLambdaの処理を行うInvokeとして活用。
④AppSyncのデータソースとしてLambdaを活用。

上記のように、functionsの設定によって様々な活用が可能となっており、サーバーレスを最大限活用するためには必須の項目です。
関数ごとに個別の設定を行うことで、API Gatewayとの結びつきやCognitoのトリガーを活用した動作を実現することができます。

1.API GateWayとして使用する

例えば、"hello"という関数のようにREST APIとしてLambdaを機能させることも可能です。
今回のREST APIでは、CognitoのAuthorizerを使用しています。Cognitoの認証が完了した後、JWT(JSON Web Token)をリクエストのヘッダーに含めないと、APIを叩くことができません。
この仕組みにより、APIのセキュリティを確保しています。

// fucntions/hello.ts

import { Context, APIGatewayProxyResult, Callback } from 'aws-lambda';
export const handler = async (event: APIGatewayProxyResult, context: Context, callback: Callback) => {
const message = 'Hello World.'
const response = {
  statusCode: 200,
  'headers': {
    "Access-Control-Allow-Origin": "*"
  },
  body: JSON.stringify({
    context: context,
    event: event,
    message: message
  })
}
callback(null, response);
};

フロントエンドでは、エンドポイントにリクエストを送信し、返ってきたレスポンスJSONをパースして処理します。

JSON.parse(res.data.body)

レスポンスのbody内の値にアクセスすることができるようになります。
serverless.ymlを使用することで、LambdaをREST APIとして簡単に利用することができます。

2.LambdaからDynamoDBをさわってみる

// functions/user.ts

import * as AWS from 'aws-sdk';
import { CognitoUserPoolTriggerEvent, Context, Callback } from 'aws-lambda';

// ------------------------------------------
// User作成完了時ユーザー情報をUserテーブルに書き込む
// ------------------------------------------
export const handler = (event: CognitoUserPoolTriggerEvent, callback: Callback) => {
    // DynamoDBを初期化
    const dynamoDB = new AWS.DynamoDB.DocumentClient({
      region: "ap-northeast-1"
    });
    // DBに保存する値
    const params = {
      TableName: 'my-serverless-backend-dev-User',
      Item: {
        'userId': event.userName,
        "username": event.userName,
        "email": event.request.userAttributes.email
      }
    }
    // DBに保存
    dynamoDB.put(params).promise().then(() => {
      callback(null, event)
    }).catch((error) => {
      callback(new Error(`${error} Couldn\'t create the todo item.`), event)
    })
}

ユーザーの新規登録後、ユーザー確認が完了すると、ユーザー情報がDynamoDBのUserテーブルに書き込まれます。
この場合、Cognitoで認証を管理しているため、アドレスなどの情報を持つ必要がないため、この方法は適切ではありません。
ただし、LambdaからDynamoDBにデータを書き込む方法として一例として紹介しています。
注意点として、DynamoDBにデータを書き込んだ後は、callback(null, event)を呼び出しましょう。
以前はcontext.done(event)と書かれている記事もありましたが、現在は非推奨とされており、エラーが発生することがあるため、今後はcallbackを使用する方が良いでしょう。

● 今から始めるServerless Frameworkでサーバーレスデビュー【バックエンド編#1】
https://note.com/ryoppei/n/n9163712b68ad

今から始めるServerless Frameworkでサーバーレスデビュー【フロントエンド編#2】
https://note.com/ryoppei/n/n0858cfca7784

今から始めるServerless Frameworkでサーバーレスデビュー【プラグイン番外編#3】
https://note.com/ryoppei/n/ne110ac6a440f

今から始めるServerless Frameworkでサーバーレスデビュー【ファンクション番外編#4】
https://note.com/ryoppei/n/n71809e2520e0

今から始めるServerless Frameworkでサーバーレスデビュー【マッピングテンプレート番外編#5】
https://note.com/ryoppei/n/n20b4afd705e5

この記事が気に入ったらサポートをしてみませんか?