LINE Bot×AWS CDKハンズオン

# はじめに

この記事は**LINE Bot**と**AWS CDK**のハンズオン資料のために作成しました。このハンズオンでは事前に以下のものの準備が必要になります。

- [AWS](https://aws.amazon.com/jp/)アカウント
  - **アクセスキーID**と**シークレットアクセスキー**が必要になります。一応この記事にも取得方法を説明します。
- [Gitpod](https://www.gitpod.io/)のアカウント 
  - [GitHub](https://github.com/)のアカウントと連携することで登録できます。
- [GitpodのChrome拡張](https://chrome.google.com/webstore/detail/gitpod-online-ide/dodmmooeoklaejobgleioelladacbeki)（推奨）
  - GitHubのソースコードを1クリックでオンラインIDEにコピーすることができます。
  - Chrome拡張を使わない場合は`gitpod.io#`をURLの先頭に付与することでIDEを開くことができます。
- [LINE Developers](https://developers.line.biz/ja/)のアカウント
  - LINE Bot作成に必要となります。

以上で本ハンズオンのための準備は完了です。ここからは**Gitpod**を用いたWEBのIDEでの作業となりますので、ソフトウェアのインストールなどの作業は不要です。

# 今回つくるもの

<img src="https://github.com/ufoo68/shopping-list-bot/blob/master/asset/clear.png?raw=true" width="200px">

このような**買い物リストBot**を作成します。具体的には以下の機能を実装します。

* 入力した単語を「買い物リスト」に追加する
* 「リスト確認」とメッセージを送ると今まで追加した「買い物リスト」の中身を確認することができる
* 「リストをクリア」とメッセージを送ると今まで今まで追加した「買い物リスト」の中身を削除することができる

今回このLINE Botの開発には[AWS](https://aws.amazon.com/jp/)というクラウドサービスを用います。AWSには様々なサービスが提供されていますが、今回用いるサービスは以下です。

* [Lambda](https://aws.amazon.com/jp/lambda/)
  * バックエンド（今回はLINE Bot）の開発に用います
* [API Gateway](https://aws.amazon.com/jp/api-gateway/)
  * LambdaをURLから呼び出すために用います
* [DynamoDB](https://aws.amazon.com/jp/dynamodb/)
  * 「買い物リスト」の保存に用います

AWS構成は以下のような感じです。

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/dfd8e27c-0ee2-5ff4-cac4-f4328b664983.png)

構成そのものとしては**Lambda**と**API Gateway**、**DynamoDB**というよくあるサーバーレスの構成となっています。今回はLambdaを**LINE Botとしての会話のやりとり**と**DynamoDBからのデータ操作**の２つの機能に分けて**Lambda Invoke**で呼び出す形で実装します。また、各Lambdaそれぞれが共通で用いる**util関数**と**npmパッケージ**を**Lambda Layer**で管理するようにします。


# CDKとは（ざっくり）

AWS-CDKとは、**プログラミング言語を使ってAWSのクラウド環境を構築**ためのオープンソースのフレームワークです。AWS-CDKを用いることでメインコンソールを使わずに各自が使い慣れた言語（今回はTypeScript）・エディタを用いたAWS開発を行うことができます。
以前に[まとめた記事](https://qiita.com/ufoo68/items/d06756b6e7bb97359074)も見てくれると嬉しいです。

今回は以下の用語を抑えてもらえるとAWS-CDKを問題なく使えるかと思います。

* [CloudFormation](https://aws.amazon.com/jp/cloudformation/)
  * AWSの構築をオートメーション化させるための設定ファイルをかくためのツールです。設定ファイルはJSONかYAMLを使って書くことができます。CDKはこのCloudFormationをプログラミング言語で書くためのツールとなってます。
* スタック
  * リソースの集まり（つまりはCDKで書いたAWSインフラの構成そのもの）を言います。CDKで書いたコードはスタックという単位で管理されます。
* デプロイ
  * ここではスタックを実際にAWSの実環境に構築することを意味します。

# ハンズオン手順

## Gitpodを立ち上げる

今回ハンズオンで用いる[GitHubのレポジトリ](https://github.com/ufoo68/shopping-list-bot/tree/handson)のページを開きます。[GitpodのChrome拡張](https://chrome.google.com/webstore/detail/gitpod-online-ide/dodmmooeoklaejobgleioelladacbeki)をインストールしていると`Gitpod`ボタンが追加されているはずなのでそこをクリックします。

![handson1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/71c8ffc6-e1ac-3127-8a9b-93a98d672127.png)

するとこんな感じ↓のvscode的なエディタが立ち上がります。これからこのGitpodを用いてCDKの開発を行ってきます。

![handson7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/f7bcb424-76d6-a4f8-3c10-17471f394f20.png)

Chrome拡張が使えない場合は[こちら](https://gitpod.io/#github.com/ufoo68/shopping-list-bot/tree/handson)をクリックしてIDEを立ち上げます。

## LINE Botチャンネルの作成

@sumihiro3 さんの[こちらの資料](https://github.com/sumihiro3/katacoda-scenarios/blob/master/LineBotBasicCourse/LineBotBasicScenario/step1.md)にわかりやすく手順が乗っておりますのでこちらを参考に進めてください。こちらで**チャネルシークレット**と**チャネルアクセストークン**を取得します。

## AWSのアクセスキーIDとシークレットアクセスキーを取得する

アクセスキーIDとシークレットアクセスキーはAWS CLIなどのコマンドラインツールでAWSを操作するのに使用します。今回はAWS-CDKを用いるのでAWS CLIのインストールは不要ですが、認証キーは当然必要なので取得しておきます。元々用意されている方はこの項目はスキップしてください。以下よりアクセスキーとシークレットアクセスキーの取得方法について説明します。

まずAWSにログインして、サービス検索欄から`IAM`を探してIAMにアクセスします。

![dandson6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/ff1678c0-d6b3-ce59-d754-4f93d158836e.png)

IAMにアクセスしたら、左メニューより`ユーザー`を選択して、`ユーザーを追加`ボタンをクリックします。

![handson2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/c32bfcb3-51ce-468e-ed8c-546781be919b.png)

そしたら`ユーザー詳細の設定`に遷移するので適当なユーザー名を指定して（今回は`handson`にした）、`アクセスの種類`に`プログラムによるアクセス`を選択します。

![handson3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/087fceb9-d030-108e-3e06-f3721347bccd.png)

次に`アクセスの許可の設定`に遷移するので、`既存のポリシーを直接アタッチ`を選択して`AdministratorAccess`を選択します。後の設定項目(3以降)は特に操作は不要なのでそのままユーザー作成に進んで大丈夫です。

![handson4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/50614810-b38d-305b-7d7b-4bf991ef4572.png)

ユーザーが作成されたらこのような画面に遷移します。ここで注意したいのが、**アクセスキーIDとシークレットアクセスキーはこの画面でしか確認できないということです。**この画面上でそれぞれをメモするか、`.csvのダウンロード`をクリックするなどを行ってください。

![handson5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/9f64dbbe-a804-1005-5214-b61f4c369091.png)

これでAWS-CDKのデプロイのための認証情報の取得は完了です。基本的にこれ以降はAWSのマネジメントコンソールを操作する作業は発生しません。

## 認証関係の設定

まずはAWS-CDKのための認証情報を設定します。普通はAWS CLIからprofileを登録するのですが今回は簡単のために以下のようにコマンドを下部のターミナルに打ち込みます。

```bash
export AWS_ACCESS_KEY_ID=アクセスキー
export AWS_SECRET_ACCESS_KEY=シークレットアクセスキー
export AWS_DEFAULT_REGION=ap-northeast-1
```

`AWS_ACCESS_KEY_ID`と`AWS_SECRET_ACCESS_KEY`は２つ前の項目で取得したア**クセスキー**と**シークレットアクセスキー**それぞれを入力してください。`AWS_DEFAULT_REGION`はAWSのリージョンを指定するものですが、今回は**東京(ap-northeast-1)**を指定します（リージョン名については[こちら](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-regions-availability-zones.html)をご参照ください）。
次にLINE Botの**チャネルシークレット**と**チャネルアクセストークン**を設定します。Gitpodの一番上の階層で`.env`というファイルを作成してください。

```:.env
ACCESS_TOKEN="チャネルアクセストークン"
CHANNEL_SECRET="チャネルシークレット"
```

`ACCESS_TOKEN`に**チャネルアクセストークン**を、`CHANNEL_SECRET`に**チャネルシークレット**をコピペします。以上で認証に関する設定は完了です。

## CDKの実装

### フォルダ構成

ここから実際にCDKの実装を行っていきます。まずは今回のハンズオン用のプロジェクトフォルダの構成の確認を行っていきます。

```
├── README.md
├── bin*
├── cdk.context.json
├── cdk.json
├── jest.config.js
├── lambda*
├── layer*
├── lib*
├── package-lock.json
├── package.json
├── tsconfig.json
└── yarn.lock
```

今回の開発に関係するものに`*`をつけました。

* bin
  * 実装したスタック(ざっくりというとAWSの構成のこと)を呼び出す上位レベルのコードが格納されています。今回は特に触りません。
* lambda
  * lambdaのコードが格納されています（今回はLINE Botに関するコード）。本ハンズオンではあらかじめコードを用意しているのでこれをCDKを実装して呼び出します。
* layer
  * lambda layerのコードが格納されています。これも実装済みですが、cdkでlambda layerを呼び出す実装を行っていきます。
* lib
  * AWSの構成に関するコードが格納されています。今回はこの`lib`をメインに実装していきます（ただし`lib/layerSetup.ts`については特に触りません）。

### 用いるコマンド

今回用いるコマンドについて簡単に説明します（[詳細](https://docs.aws.amazon.com/cdk/latest/guide/tools.html)）。

#### build

TypeScriptで書いたスタックをJavaScriptのコードに変換します。これはTypeScriptに関係するコマンドですが、デプロイ前に必ず必要となるのでお忘れなく。

```bash
yarn build
```

別ターミナルで以下を実行すると常にビルドが走るようになるのでこちらを使うと良いかもです。

```bash
yarn watch
```

#### bootstrap

CDKで書いたスタック（正式にはCloudFormation）をデプロイするためのS3を用意する。とりあえず最初に実行するやつ。

```bash
yarn cdk bootstrap
```

#### deploy

CDKで書いたスタックをデプロイする。これが一番使うやつ。

```bash
yarn cdk deploy
```

#### destroy

CDKで書いたスタックを削除する。最後に実装するやつ。

```bash
yarn cdk destroy
```

他にも種類はありますが、今回用いるコマンドはこの３つです。

### 最初に打って欲しいコマンド

ここから本格に実装に入っていきますが、最初に２つのコマンドを実行してください。

```bash
yarn
```

↑で`package.json`の中の必要パッケージをインストールします

```bash
yarn cdk bootstrap
```

↑でデプロイのための準備をします。ここで何かしらのエラーが発生した場合、AWSの認証情報の設定にミスがある可能性があるので確認をお願いします。

### ２つのLambdaを用意する

ここから実装です。まずはLINE Botのための**会話用**と**買い物リストの操作**のLambdaを２つ用意します。

```typescript:lib/cdk-line-bot-stack.ts
export class CdkLineBotStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const dbHandler = new lambda.Function(this, 'DbHandlerFunction', {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/dbHandler'), // コードのディレクトリ
    })

    const linebot = new lambda.Function(this, 'LineBotFunction', {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/linebot'), // コードのディレクトリ
    })

  }
}
```

今の構成を図にすると↓こんな感じです。単純に２つのLambdaが並んでいるだけです。

![Untitled(1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/8d1fa47b-3fd0-75c8-23f1-295cc4b6c08b.png)

### DynamoDBを用意する

買い物リストを実装するためには何かしらのデータベースが必要になるので、DynamoDBを用意します。

```typescript:lib/cdk-line-bot-stack.ts
export class CdkLineBotStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const table = new dynamodb.Table(this, 'Table', {
      partitionKey: { name: 'userId', type: dynamodb.AttributeType.STRING }
    })

    const dbHandler = new lambda.Function(this, 'DbHandlerFunction', {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/dbHandler'),
    })

    const linebot = new lambda.Function(this, 'LineBotFunction', {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/linebot'),
    })

  }
}
```

今の構成を図にすると↓こんな感じです。単純に３つのリソースが並んでいるだけですね。

![Untitled(2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/d0f294a1-a368-579b-079f-3d30748ffc15.png)

### API Gatewayを設置する

LINE Botの作成のために、LambdaをPOSTリクエストで呼び出し可能にしたいのでAPI Gatewayを用意します。

```typescript:lib/cdk-line-bot-stack.ts
export class CdkLineBotStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const table = new dynamodb.Table(this, 'Table', {
      partitionKey: { name: 'userId', type: dynamodb.AttributeType.STRING }
    })

    const dbHandler = new lambda.Function(this, 'DbHandlerFunction', {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/dbHandler'),
    })

    const linebot = new lambda.Function(this, 'LineBotFunction', {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/linebot'),
    })

    const api = new apigateway.RestApi(this, 'Api')
    api.root.addMethod('POST', new apigateway.LambdaIntegration(linebot))

  }
}
```

今の構成を図にすると↓こんな感じです。１つの会話用のLambdaがPOSTリクエストで呼び出せるようになりました。

![Untitled(3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/0395311b-4900-83a8-27e9-62913f978887.png)

### Lambda layerを用意する

２つのLambdaで`layer`のutil関数・npmパッケージを共有するためにLambda layerを用意します。

```typescript:lib/cdk-line-bot-stack.ts
export class CdkLineBotStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const table = new dynamodb.Table(this, 'Table', {
      partitionKey: { name: 'userId', type: dynamodb.AttributeType.STRING }
    })

    const layer = new lambda.LayerVersion(this, 'layer', {
      compatibleRuntimes: [lambda.Runtime.NODEJS_12_X],
      code: lambda.Code.fromAsset('layer.out'),
    })

    const dbHandler = new lambda.Function(this, 'DbHandlerFunction', {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/dbHandler'),
      layers: [layer], // layerの指定
    })

    const linebot = new lambda.Function(this, 'LineBotFunction', {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/linebot'),
      layers: [layer], // layerの指定
    })

    const api = new apigateway.RestApi(this, 'Api')
    api.root.addMethod('POST', new apigateway.LambdaIntegration(linebot))

  }
}
```

ここからコードの差分がわかりにくくなってくるかもしれません。実装内容としては`layer`というクラスを定義して、２つのLambdaに設定しています。ここまでですべてのリソースが揃ったので下の図ようになります。

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/94d41142-55f3-d606-fa96-8b5e46af33a5.png)

### 環境変数の受け渡し

ここから各リソースのつなぎ込み作業に入ります。まず、各Lambdaにはそれぞれ知ってなければならない情報があります。

* 会話用Lambda（`linebot`）
  * LINE botに関する認証情報（チャネルシークレットとチャネルアクセストークン）
  * 買い物リスト操作用のLambda名
* 買い物リスト操作用Lambda（`dbHnadler`）
  * DynamoDBのテーブル名

なのでそれぞれを環境変数で受け渡します。

```typescript:lib/cdk-line-bot-stack.ts
export class CdkLineBotStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const table = new dynamodb.Table(this, 'Table', {
      partitionKey: { name: 'userId', type: dynamodb.AttributeType.STRING }
    })

    const layer = new lambda.LayerVersion(this, 'layer', {
      compatibleRuntimes: [lambda.Runtime.NODEJS_12_X],
      code: lambda.Code.fromAsset('layer.out'),
    })

    const dbHandler = new lambda.Function(this, 'DbHandlerFunction', {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/dbHandler'),
      layers: [layer],
      environment: { // 環境変数の設定
        TABLE_NAME: table.tableName,
      }
    })

    const linebot = new lambda.Function(this, 'LineBotFunction', {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/linebot'),
      layers: [layer],
      environment: { // 環境変数の設定
        ACCESS_TOKEN: process.env.ACCESS_TOKEN!,
        CHANNEL_SECRET: process.env.CHANNEL_SECRET!,
        FUNCTION_NAME: dbHandler.functionName,
      }
    })

    const api = new apigateway.RestApi(this, 'Api')
    api.root.addMethod('POST', new apigateway.LambdaIntegration(linebot))

  }
}
```

一見これですべてのリソースのつなぎ込みができたかのように見えますが、実際は↓こんな感じです。

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/e63edbfc-3753-83fe-fe54-098b1958a98c.png)

### ロールの付与

Lambdaが他のAWSリソース（ここではLambdaやDynamoDB）にアクセスするにはロールを与える必要があります。CDKではロールの受け渡しは１行で書くことができます。

```typescript:lib/cdk-line-bot-stack.ts
export class CdkLineBotStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const table = new dynamodb.Table(this, 'Table', {
      partitionKey: { name: 'userId', type: dynamodb.AttributeType.STRING }
    })

    const layer = new lambda.LayerVersion(this, 'layer', {
      compatibleRuntimes: [lambda.Runtime.NODEJS_12_X],
      code: lambda.Code.fromAsset('layer.out'),
    })

    const dbHandler = new lambda.Function(this, 'DbHandlerFunction', {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/dbHandler'),
      layers: [layer],
      environment: {
        TABLE_NAME: table.tableName,
      }
    })

    const linebot = new lambda.Function(this, 'LineBotFunction', {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/linebot'),
      layers: [layer],
      environment: {
        ACCESS_TOKEN: process.env.ACCESS_TOKEN!,
        CHANNEL_SECRET: process.env.CHANNEL_SECRET!,
        FUNCTION_NAME: dbHandler.functionName,
      }
    })

    dbHandler.grantInvoke(linebot) // Lambdaの呼び出し権限付与

    table.grantFullAccess(dbHandler)  // DynamoDBの操作権限付与

    const api = new apigateway.RestApi(this, 'Api')
    api.root.addMethod('POST', new apigateway.LambdaIntegration(linebot))

  }
}
```

これで↓のような構成を完成させることができました。

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/8bbb7840-dfc0-9a23-796b-abbaf5e88041.png)

### デプロイ

後は以下のコマンドでデプロイします。デプロイ時になにかCDKに聞かれると思うのですべて`y`で答えてください。

```bash
yarn build
yarn cdk deploy
```

API Gatewayのあるスタックをデプロイするとデプロイ成功後にURLがターミナルに表示されます。このURLがLINE BotのWebhook用のURLとしてそのまま用いるのでコピーしておきましょう。
後は[この資料](https://github.com/sumihiro3/katacoda-scenarios/blob/master/LineBotBasicCourse/LineBotBasicScenario/step1.md)を参考にしながらURLを設定すればLINE Botは完成です。認証情報関係に問題がなければ**買い物リストbot**が動くはずです。

### おまけ：Lambdaを共通化しよう

おそらくLambdaに関するCDKのコードを見ていると共通化したくなる部分があると感じるかと思います。このときに用いるのが`Construct`と呼ばれるライブラリ化の機能です（[詳細](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html)）。Constructを用いた共通化の例を以下に示します。今回は`lib/lambdaUtil.ts`で新たにファイルを作成して`LambdaUtil`という共通ライブラリを作成することにします。

```typescript:lib/lambdaUtil.ts
import * as cdk from '@aws-cdk/core'
import * as lambda from '@aws-cdk/aws-lambda'

interface LambdaUtilProps {
  layer: lambda.ILayerVersion
  path: string,
  environment?: {
    [key: string]: string
  },
}

export class LambdaUtil extends cdk.Construct {
  public readonly handler: lambda.Function
  constructor(scope: cdk.Construct, id: string, props: LambdaUtilProps) {
    super(scope, id)

    const { layer, environment, path } = props

    this.handler = new lambda.Function(this, id, {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset(path),
      layers: [layer],
      environment,
    })
  }
}
```

`LambdaUtilProps`で定義したものがこのライブラリに与えるパラメータです。`runtime`や`handler`が共通化されています。
そして`lib/cdk-line-bot-stack.ts`を以下のように書き換えます。

```typescript:lib/cdk-line-bot-stack.ts
import * as cdk from '@aws-cdk/core'
import * as lambda from '@aws-cdk/aws-lambda'
import * as apigateway from '@aws-cdk/aws-apigateway'
import * as dynamodb from '@aws-cdk/aws-dynamodb'
import { LambdaUtil } from './lambdaUtil'

export class CdkLineBotStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const table = new dynamodb.Table(this, 'Table', {
      partitionKey: { name: 'userId', type: dynamodb.AttributeType.STRING }
    })

    const layer = new lambda.LayerVersion(this, 'layer', {
      compatibleRuntimes: [lambda.Runtime.NODEJS_12_X],
      code: lambda.Code.fromAsset('layer.out'),
    })

    const dbHandler = new LambdaUtil(this, 'DbHandlerFunction', {
      path: 'lambda/dbHandler',
      layer,
      environment: {
        TABLE_NAME: table.tableName,
      },
    }).handler

    const linebot = new LambdaUtil(this, 'LineBotFunction', {
      path: 'lambda/linebot',
      layer,
      environment: {
        ACCESS_TOKEN: process.env.ACCESS_TOKEN!,
        CHANNEL_SECRET: process.env.CHANNEL_SECRET!,
        FUNCTION_NAME: dbHandler.functionName,
      }
    }).handler

    dbHandler.grantInvoke(linebot)

    table.grantFullAccess(dbHandler)

    const api = new apigateway.RestApi(this, 'Api')
    api.root.addMethod('POST', new apigateway.LambdaIntegration(linebot))

  }
}

```

今回はあまり見た目に変化がありませんが、共通化の機能を用いることで実装ミスを減らすことができます。

# さいごに

[全体のコードはこちら](https://github.com/ufoo68/shopping-list-bot)で用意しています。また、デプロイしたスタックを削除したい場合は、

```bash
yarn cdk destroy
```

を実行すれば削除することができます。