今から始めるServerless Frameworkでサーバーレスデビュー【バックエンド編#1】
この記事では、前編と後編に分けて「ServerlessFrameworkを使ったサーバーレス環境の構築」について紹介していきます。
前編では、ServerlessFrameworkを利用してAWS上でサーバーレス環境を構築します。
後編では、フロントエンドにReactを使用し、Amplifyを介してバックエンドと接続し、簡単な認証やCRUD操作ができるようにします。
1.SeverlessFrameworkとは?
Serverless Frameworkは、サーバーレスなアーキテクチャを簡単に構築するためのオープンソースのフレームワークです。
AWS Lambda、Azure Functions、Google Cloud Functionsなど、さまざまなプラットフォームに対応しています。
2.Serverless Frameworkのインストール
Serverless Frameworkは、npmを介して提供されており、-gオプションを使用してグローバル環境にインストールします。
$ npm install serverless -g
インストールが完了したら、次はServiceを作成します。
$ serverless create --template aws-nodejs --path my-serverless-backend
Serverless: Generating boilerplate...
_______ __
| _ .-----.----.--.--.-----.----| .-----.-----.-----.
| |___| -__| _| | | -__| _| | -__|__ --|__ --|
|____ |_____|__| \___/|_____|__| |__|_____|_____|_____|
| | | The Serverless Application Framework
| | serverless.com, v2.0.0
-------'
Serverless: Successfully generated boilerplate for template: "aws-nodejs"
Serverless: NOTE: Please update the "service" property in serverless.yml with your service name
templateオプションに使用するサービス名を指定し、pathオプションを指定することでディレクトリを作成します。
作成されたymlファイルに設定を書いていきます。ただし、インデントのミスに注意しましょう。
# ------------------------------------------
# service
# サービス名を設定
# ------------------------------------------
service: my-serverless-backend
# ------------------------------------------
# plugins
# サービスに必要なプラグインを設定
# ------------------------------------------
plugins:
- aws-amplify-serverless-plugin
- serverless-appsync-plugin
- serverless-plugin-typescript
# ------------------------------------------
# package
# サービスから除外するディレクトリを設定
# ------------------------------------------
package:
exclude:
- ./node_modules/**
- node_modules/**
# ------------------------------------------
# frameworkVersion
# フレームワークのバージョンを設定
# ------------------------------------------
frameworkVersion: '2.0.0'
# ------------------------------------------
# provider
# サービスの基本情報を設定
# ------------------------------------------
provider:
name: aws
runtime: nodejs12.x
stage: ${opt:stage, "dev"}
region: ap-northeast-1
apiGateway:
minimumCompressionSize: 1024
environment:
TABLE_POST: ${self:service}-${opt:stage, self:provider.stage}-Post
iamRoleStatements:
- Effect: Allow
Action:
- "dynamodb:*"
Resource:
- 'arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/*'
- 'arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/*/*'
- Effect: Allow
Action:
- lambda:*
Resource:
- 'arn:aws:lambda:${opt:region, self:provider.region}:*:function:*'
- Effect: 'Allow'
Action:
- 'cognito-idp:*'
Resource: 'arn:aws:cognito-idp:*'
# ------------------------------------------
# functions
# ラムダで使用する関数を設定
# ------------------------------------------
functions:
# 初期ファンクション
hello:
handler: functions/hello.handler
memorySize: 128
timeout: 10
events:
- http:
path: /hello
method: get
integration: lambda
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
# ------------------------------------------
# custom
# カスタムするサービス郡を設定
# ------------------------------------------
custom:
amplify:
- filename: ./src/aws-exports.js
type: javascript
appClient: UserPoolClient
appSync:
name: ${self:service}-${self:provider.stage}
authenticationType: AMAZON_COGNITO_USER_POOLS
userPoolConfig:
awsRegion: ap-northeast-1
defaultAction: ALLOW
region: ap-northeast-1
userPoolId:
Ref: UserPool
mappingTemplatesLocation: mapping-templates
mappingTemplates:
# ------------------------------------------
# Queries
# ------------------------------------------
-
type: Query
field: getPost
dataSource: Post
-
type: Query
field: listPost
dataSource: Post
-
type: Query
field: getUser
dataSource: User
# ------------------------------------------
# Mutations
# ------------------------------------------
-
type: Mutation
field: createPost
dataSource: Post
-
type: Mutation
field: updatePost
dataSource: Post
-
type: Mutation
field: deletePost
dataSource: Post
schema: schema.graphql
dataSources:
- type: AMAZON_DYNAMODB
name: User
config:
tableName: ${self:provider.environment.TABLE_USER}
serviceRoleArn: { Fn::GetAtt: [AppSyncDynamoDBServiceRole, Arn] }
region: ap-northeast-1
- type: AMAZON_DYNAMODB
name: Post
config:
tableName: ${self:provider.environment.TABLE_POST}
serviceRoleArn: { Fn::GetAtt: [AppSyncDynamoDBServiceRole, Arn] }
region: ap-northeast-1
# ------------------------------------------
# resources
# サービス郡の詳細リソースを設定
# ------------------------------------------
resources:
- ${file(resources/cognito-user-pool.yml)}
- ${file(resources/dynamodb-table.yml)}
- ${file(resources/i-am-role.yml)}
- ${file(resources/authorizer.yml)}
セクションごとにポイントを説明します。
// resources/cognito-user-pool.yml
Resources:
UserPool:
Type: 'AWS::Cognito::UserPool'
Properties:
UserPoolName: ${self:service}-user-pool-${opt:stage, self:provider.stage}
UsernameAttributes:
- email
AccountRecoverySetting:
RecoveryMechanisms:
- Name: verified_email
Priority: 1
AdminCreateUserConfig:
AllowAdminCreateUserOnly: false
AutoVerifiedAttributes:
- email
MfaConfiguration: 'OFF'
Policies:
PasswordPolicy:
MinimumLength: 8
RequireLowercase: false
RequireNumbers: false
RequireSymbols: false
RequireUppercase: false
TemporaryPasswordValidityDays: 7
Schema:
- AttributeDataType: String
DeveloperOnlyAttribute: false
Mutable: true
Name: email
Required: true
VerificationMessageTemplate:
DefaultEmailOption: CONFIRM_WITH_CODE
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
ClientName: ${self:service}-user-pool-client-${opt:stage, self:provider.stage}
UserPoolId:
Ref: UserPool
GenerateSecret: false
# Cognito - Lambda
CognitoTriggerLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName:
Fn::GetAtt:
- CustomMessagesLambdaFunction
- Arn
Principal: cognito-idp.amazonaws.com
// resources/i-am-role.yml
Resources:
# AppSync - DynamoDB
AppSyncDynamoDBServiceRole:
Type: "AWS::IAM::Role"
Properties:
RoleName: ${opt:stage, self:provider.stage}-appsync-dynamodb-role
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "appsync.amazonaws.com"
Action:
- "sts:AssumeRole"
Policies:
- PolicyName: "dynamo-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "dynamodb:Query"
- "dynamodb:BatchWriteItem"
- "dynamodb:GetItem"
- "dynamodb:DeleteItem"
- "dynamodb:PutItem"
- "dynamodb:Scan"
- "dynamodb:UpdateItem"
Resource:
- 'arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/*'
- 'arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/*/*'
// resources/authorizer.yml
ApiGatewayAuthorizer:
Type: AWS::ApiGateway::Authorizer
Properties:
Name: ApiGatewayAuthorizer
Type: COGNITO_USER_POOLS
IdentitySource: method.request.header.Authorization
RestApiId:
Ref: ApiGatewayRestApi
ProviderARNs:
- { Fn::GetAtt: [UserPool, Arn] }
GatewayResponse401:
Type: 'AWS::ApiGateway::GatewayResponse'
Properties:
ResponseParameters:
gatewayresponse.header.Access-Control-Allow-Origin: "'*'"
gatewayresponse.header.Access-Control-Allow-Headers: "'*'"
ResponseType: UNAUTHORIZED
RestApiId:
Ref: 'ApiGatewayRestApi'
StatusCode: '401'
GatewayResponse500:
Type: 'AWS::ApiGateway::GatewayResponse'
Properties:
ResponseParameters:
gatewayresponse.header.Access-Control-Allow-Origin: "'*'"
gatewayresponse.header.Access-Control-Allow-Headers: "'*'"
ResponseType: AUTHORIZER_FAILURE
RestApiId:
Ref: 'ApiGatewayRestApi'
StatusCode: '500'
// resources/dynamodb-table.yml
Resources:
DynamoDBUser:
Type: 'AWS::DynamoDB::Table'
Properties:
TableName: ${self:provider.environment.TABLE_USER}
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
DynamoDBPost:
Type: 'AWS::DynamoDB::Table'
Properties:
TableName: ${self:provider.environment.TABLE_POST}
AttributeDefinitions:
- AttributeName: postId
AttributeType: S
KeySchema:
- AttributeName: postId
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
これにより、各設定が完了しました。
$ serverless deploy -v // aliasを活用してsls deploy -vとすることも可能
コマンドを入力すると、CloudFormation上にスタックが作成され、環境が構築されます。
これだけで、AWS上にコードベース以下のリソースが簡単に作成されました。
3.今回のディレクトリ構造
├── .severless // deploy後に生成されるディレクトリ
├── functions // LambdaのFunctionsディレクトリ
│ ├── cognito.ts
│ └── hello.ts
│ └── user.ts
├── mapping-templates // appSyncのCRUDで必要なリゾルバーディレクトリ
│ ├── Mutation.createPost.request.vtl
│ ├── Mutation.createPost.response.vtl
│ ├── Mutation.deletePost.request.vtl
│ ├── Mutation.deletePost.response.vtl
│ ├── Mutation.updatePost.request.vtl
│ ├── Mutation.updatePost.response.vtl
│ ├── Query.getUser.request.vtl
│ ├── Query.getUser.response.vtl
│ ├── Query.getPost.request.vtl
│ ├── Query.getPost.response.vtl
│ ├── Query.listPost.request.vtl
│ └── Query.listPost.response.vtl
├── package.json
├── resources // 各サービスの詳細インフラファイルがあるディレクトリ
│ ├── cognito-user-pool.yml
│ ├── dynamodb-table.yml
│ └── i-am-role.yml
│ └── authorizer.yml
├── schema.graphql // appSyncのGraphQLで使用するスキーマファイル
├── serverless.yml // serverlessframeworkの設定ファイル
├── src
│ └── aws-exports.js // amplifyに繋ぐための設定ファイル
├── tsconfig.json
└── yarn.lock
Serverless Frameworkを活用することで、ステージングやローカル環境など、異なる環境での構築が簡単に行えることがわかりました。
また、コードベースでのインフラアーキテクチャを構築できるなど、多くのメリットがあります。
これらの理由から、サーバーレスを本格的に活用する際には、Serverless Frameworkは有力な選択肢となるでしょう。
本記事と合わせて、Serverless Frameworkを活用するポイントを押さえてお読みいただければ幸いです。
この記事が気に入ったらサポートをしてみませんか?