alexaスキルをvisual studio codeでデバッグする(DynamoDBを使う場合)


前回に引き続き、今度はDynamo DBを使ったスキルをローカルデバッグする環境を作っていきます。
前回と同じく、alexa-hostedスキルの場合です。

DynamoDBを使うためのコードは公式ドキュメントの通りで本番環境だと問題なく動くのですが、vscodeに持ってくると全く動いてくれないので、順番に設定していきます。

環境

環境を再掲しておきます。前回と同じです。

  • OS mac OS 10.15.7(Catalina)
  • visual studio code 1.59.1
  • python 3.9.4 (homebrewからインストール)
  • ASK Toolkit Extension 2.8.0

1. pythonパッケージのインストール

公式ドキュメントのようにrequirements.txtを修正して、パッケージをインストールします。

lambda/requirements.txt
boto3==1.9.216
ask-sdk-core==1.11.0
ask-sdk-dynamodb-persistence-adapter==1.15.0
$ pip install -r requirements.txt

2. 環境変数の設定

本番環境ではDynamoDBにアクセスするために環境変数を使っていますので、本番環境の環境変数を取得するために、以下のようにlambda_function.pyを変更します。

lambda_function.py
import os
...
print("environments:", os.environ)
ddb_region = os.environ.get('DYNAMODB_PERSISTENCE_REGION')
ddb_table_name = os.environ.get('DYNAMODB_PERSISTENCE_TABLE_NAME')
...

deployして、本番環境でsimulatorテストしてみてください。

ところで、本番環境でprint文を実行した結果はどこに行くのでしょうか。
答えはCloudWatchログです。web上のalexa developer consoleの「コードエディタ」タグにCloudWatchへのリンクがありますので、そこから開くことができます。
ただ私の環境ではデフォルトではus-east-1(バージニア北部)リージョンのページが開くのですが、そのままだと「ロググループが存在しません」というエラーが出てしまい、ログをみることができなくて悩んでいました。
私の環境ではリージョンをus-west-2(オレゴン)にするとログを見ることができました。(リージョンはCloudWatchページの右上あたりで選択することができます)

さて私の環境では環境変数は
- DYNAMODB_PERSISTENCE_REGION=us-east-1
- DYNAMODB_PERSISTENCE_TABLE_NAME=1ca5e6da-87d5-47e7-a25d-e46599c41fb
になっています。
ちなみにテーブル名はスキルIDに一致しています。

launch.jsonを変更して、環境変数を設定します。後述の理由で、テーブル名の方だけ設定します。

.vscode/launch.json
...
    "configurations": [
        {
            "name": "Debug Alexa Skill (Python)",
            "type": "python",
            "request": "launch",
            "program": "${command:ask.debugAdapterPath}",
            "pythonPath": "${command:python.interpreterPath}",
            "args": [
                "--accessToken",
                "${command:ask.accessToken}",
                "--skillId",
                "${command:ask.skillIdFromWorkspace}",
                "--skillHandler",
                "lambda_handler",
                "--skillFilePath",
                "${workspaceFolder}/lambda/lambda_function.py",
                "--region",
                "FE"
            ],
            "console": "internalConsole",
            "cwd": "${workspaceFolder}/lambda",
            "justMyCode": false,
            "env": {
                "DYNAMODB_PERSISTENCE_TABLE_NAME" : "1ca5e6da-87d5-47e7-a25d-e46599c41fbf"
            }
        },
...

3. .awsフォルダの設定

$HOMEディレクトリに、.awsというディレクトリを作ります。すでにあるかもしれません。
そこにcredentialsファイルとconfigファイルを作成します。

$HOME/.aws/credentials
[default]
aws_access_key_id=XXXXXX
aws_secret_access_key=XXXXXXXXX
$HOME/.aws/config
[default]
region=us-east-1

後述しますがDynamoDBローカルを使うので、credentialsの値は何でも構いません。すでに値が入っていればそのままでいいと思います。
configのregionは環境変数DYNAMODB_PERSISTENCE_REGIONに合わせておいた方がいいかと思います。

この設定をしていないと、DynamoDbAdapterのimportの時点でNoRegionErrorがraiseされてしまいます。

4. DynamoDBローカル

本来なら、本番環境と同じDynamoDBにアクセスできる方がテスト環境としてはいいと思うのですが、残念ながらうまくいきませんでした。
というのも、CloudWatchログも同じなので上記画像を見るとわかるかと思いますが、alexaスキルがアクセスするDynamoDBはVoiceHubSSORole?にフェデレーションログインしてアクセスするもので、個人アカウントとは別のアカウントになります。
この特殊アカウントではIAM(Identity and Access Management)を設定する権限もありませんし、credentialが取得できないのでローカルからアクセスする方法がわかりませんでした。
こういったデバッグをしたい場合は、alexa-hostedではなくてawsのlambdaを使ってねという事なのでしょうか…

そういう理由でローカルにDynamoDBサーバーを立てて、デバッグではこちらを使う事にします。
やり方はawsドキュメントの通りなのですが、今回はdockerを使ってみようと思います。
dockerはdocker公式サイトからインストールしています。
適当なディレクトリを作り、docker-compose.ymlというファイルを作ります。

docker-compose.yml
version: '3.8'
services:
  dynamodb-local:
    command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data"
    image: "amazon/dynamodb-local:latest"
    container_name: dynamodb-local
    ports:
      - "8000:8000"
    volumes:
      - "./docker/dynamodb:/home/dynamodblocal/data"
    working_dir: /home/dynamodblocal

起動します

$ docker-compose up
Creating network "dynamodb-local_default" with the default driver
Pulling dynamodb-local (amazon/dynamodb-local:latest)...
latest: Pulling from amazon/dynamodb-local
2cbe74538cb5: Pull complete
137077f50205: Pull complete
58932e640a40: Pull complete
Digest: sha256:bdd26570dc0e0ae49e1ea9d49ff662a6a1afe9121dd25793dc40d02802e7e806
Status: Downloaded newer image for amazon/dynamodb-local:latest
Creating dynamodb-local ... done
Attaching to dynamodb-local
dynamodb-local    | Initializing DynamoDB Local with the following configuration:
dynamodb-local    | Port:   8000
dynamodb-local    | InMemory:   false
dynamodb-local    | DbPath: ./data
dynamodb-local    | SharedDb:   true
dynamodb-local    | shouldDelayTransientStatuses:   false
dynamodb-local    | CorsParams: *
dynamodb-local    | 

5. テーブルの作成

DynamoDBローカルサーバーにテーブルを作成します。
下記pythonプログラムを実行してください。(table_nameは変更してください)

import boto3

dynamodb = boto3.resource('dynamodb', endpoint_url="http://localhost:8000")

table_name = "1ca5e6da-87d5-47e7-a25d-e46599c41fbf" 
dynamodb.create_table(
    TableName=table_name,
    KeySchema=[
        {
            'AttributeName': 'id',
            'KeyType': 'HASH'  # Partition key
        }
    ],
    AttributeDefinitions=[
        {
            'AttributeName': 'id',
            'AttributeType': 'S'
        },
    ],
    ProvisionedThroughput={
        'ReadCapacityUnits': 10,
        'WriteCapacityUnits': 10
    }
)

テーブルを作ると、先ほどdockerを立ち上げたディレクトリーの下に、docker/dynamodb/shared-local-instance.dbというファイルが作成されます。

6. lambda_function.pyの変更

最後に、vscodeでデバッグ中はDynamoDBローカルを使うようにコードを変更します。
デバッグ中かどうかを判定するために、環境変数DYNAMODB_PERSISTENCE_REGIONがあるかどうかで判断する事にします。
そのため、先ほどlaunch.jsonにこの環境変数を設定しませんでした。

lambda_function.py
ddb_region = os.environ.get('DYNAMODB_PERSISTENCE_REGION')
ddb_table_name = os.environ.get('DYNAMODB_PERSISTENCE_TABLE_NAME')

from ask_sdk_dynamodb.adapter import DynamoDbAdapter

if ddb_region :
    ddb_resource = boto3.resource('dynamodb', region_name=ddb_region)
else :
    ddb_resource = boto3.resource('dynamodb', endpoint_url="http://localhost:8000")

dynamodb_adapter = DynamoDbAdapter(table_name=ddb_table_name, create_table=False, dynamodb_resource=ddb_resource)

他の部分は公式ドキュメント通りで動いてくれました。

まとめ

以上で、DynamoDBを使ってもvscodeでデバッグできるようになりました。
直接サーバーのDynamoDBを使う方法がないかと色々調べるのに苦労しましたが、できないとあきらめれば何とかなりました。
DynamoDBで調べていても、alexa-hostedの場合なのかawsの場合なのかわかりずらくて、なかなかいい情報に行き当たらなかったのでまとめてみました。
どなたかの助けになれば幸いです。