Alexaを育てる！配列からデータベースへバージョンアップ

# まずは動かしてみた
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">話してることは同じだけど、データベース見るように中身変えました。<a href="https://twitter.com/hashtag/awslambda?src=hash&amp;ref_src=twsrc%5Etfw">#awslambda</a> <a href="https://twitter.com/hashtag/dynamodb?src=hash&amp;ref_src=twsrc%5Etfw">#dynamodb</a> <a href="https://twitter.com/hashtag/alexa?src=hash&amp;ref_src=twsrc%5Etfw">#alexa</a> <a href="https://twitter.com/hashtag/%E5%88%9D%E5%BF%83%E8%80%85%E3%81%A7%E3%81%99?src=hash&amp;ref_src=twsrc%5Etfw">#初心者です</a> <a href="https://t.co/wiDwBCjHfn">pic.twitter.com/wiDwBCjHfn</a></p>&mdash; heihei (@heihei15408697) <a href="https://twitter.com/heihei15408697/status/1336596764472659969?ref_src=twsrc%5Etfw">December 9, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 前と何が変わったの？
実は前とAlexaが話している内容は変わりがない。
↓前回の記事
https://qiita.com/heihei15408697/items/6ce26775b26daed044e4

変えたのは作りの部分。前回の構成は↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/726018/cc15992f-fe36-e52c-e133-6370ed2931b7.png)

今回の構成は↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/726018/2571c93f-888d-577b-badc-0245904ac275.png)

**Node.js上で配列にしていたものをデータベース使うように変えた！！**

# 実際に苦労した点は・・・
下記のQiita記事にしてるのでそちら参照！
・[「Alexa、Lambdaと連携する方法を教えて」と聞いても、答えがよく分からなかったので自分でやったことメモ](https://qiita.com/heihei15408697/items/4fee419fdc22cb011b0b)
・[LambdaからDynamoDBを使うときの権限エラー解消メモ](https://qiita.com/heihei15408697/items/faec9ffceecaee7f6be0)
・[Lambda→DynamoDBが非同期処理と知らないやつがとった行動](https://qiita.com/heihei15408697/items/7fd898cac42187da215d)

# これをやってみての感想
データベースを導入することで一気にシステムっぽくなる。
ちょっと動かすだけなら、前回までの配列でいいと思うけど、メンテナンスを考えると、やっぱりデータベースがいいんだろうな。
あと、僕個人としてはLambdaもDynamoDBも使ったことなかったので、すごく勉強になったし、楽しかった！！

# Lambdaで動かしているNode.jsのソース
※Lambdaに取り込む際にnpmでインストールしたask-sdk-coreとi18nextをすべてzipにして取り込んでます。

```javascript

// 使用するモジュールを定義
const Alexa = require('ask-sdk-core');
const i18n = require('i18next');

const {
  getRequestType,
  getIntentName,
  getSlotValue,
  getDialogState,
} = require('ask-sdk-core');

const AWS = require('aws-sdk');
const documentClient = new AWS.DynamoDB.DocumentClient()


const GetNewFactHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'LaunchRequest'
      || (request.type === 'IntentRequest'
        && request.intent.name === 'GetNewFactIntent');
  },
  handle(handlerInput) {
    const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
    const randomFact = requestAttributes.t('FACTS');
    const speakOutput = requestAttributes.t('GET_FACT_MESSAGE') + randomFact;

    return handlerInput.responseBuilder
      .speak(speakOutput)
      .withSimpleCard(requestAttributes.t('SKILL_NAME'), randomFact)
      .getResponse();
  },
};

const HelpHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
      && request.intent.name === 'AMAZON.HelpIntent';
  },
  handle(handlerInput) {
    const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
    return handlerInput.responseBuilder
      .speak(requestAttributes.t('HELP_MESSAGE'))
      .reprompt(requestAttributes.t('HELP_REPROMPT'))
      .getResponse();
  },
};

const FallbackHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
      && request.intent.name === 'AMAZON.FallbackIntent';
  },
  handle(handlerInput) {
    const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
    return handlerInput.responseBuilder
      .speak(requestAttributes.t('FALLBACK_MESSAGE'))
      .reprompt(requestAttributes.t('FALLBACK_REPROMPT'))
      .getResponse();
  },
};

const ExitHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
      && (request.intent.name === 'AMAZON.CancelIntent'
        || request.intent.name === 'AMAZON.StopIntent');
  },
  handle(handlerInput) {
    const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
    return handlerInput.responseBuilder
      .speak(requestAttributes.t('STOP_MESSAGE'))
      .getResponse();
  },
};

const SessionEndedRequestHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'SessionEndedRequest';
  },
  handle(handlerInput) {
    console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`);
    return handlerInput.responseBuilder.getResponse();
  },
};

const ErrorHandler = {
  canHandle() {
    return true;
  },
  handle(handlerInput, error) {
    console.log(`Error handled: ${error.message}`);
    console.log(`Error stack: ${error.stack}`);
    const requestAttributes = handlerInput.attributesManager.getRequestAttributes();
    return handlerInput.responseBuilder
      .speak(requestAttributes.t('ERROR_MESSAGE'))
      .reprompt(requestAttributes.t('ERROR_MESSAGE'))
      .getResponse();
  },
};

const LocalizationInterceptor = {
  process(handlerInput) {
    const localizationClient = i18n.init({
      lng: handlerInput.requestEnvelope.request.locale,
      resources: languageStrings,
      returnObjects: true
    });
    localizationClient.localize = function localize() {
      const args = arguments;
      const value = i18n.t(...args);
      if (Array.isArray(value)) {
        return value[Math.floor(Math.random() * value.length)];
      }
      return value;
    };
    const attributes = handlerInput.attributesManager.getRequestAttributes();
    attributes.t = function translate(...args) {
      return localizationClient.localize(...args);
    }
  }
};

const HelloWorldIntentHandler = {
  canHandle(handlerInput) {
    console.log('called HelloWorldIntentHandler.canHandle');
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
　          && request.intent.name === 'HelloWorldIntent';
  },
  handle(handlerInput) {
    console.log('called HelloWorldIntentHandler.handle');
    return handlerInput.responseBuilder
            .speak("こんにちはインテントが呼ばれました。")
            .getResponse();
  }
};

//　今回の処理
const SearchSkillHandler = {
  canHandle(handlerInput) {
    console.log('called SearchSkillHandler.canHandle');
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
　          && request.intent.name === 'SearchSkillIntent';
  },
  // DynamoDBからのデータ取得処理は非同期だったので、関数にasyncを設定
  async handle(handlerInput) {
    const skillsNameValue = getSlotValue(handlerInput.requestEnvelope, 'skills');
    console.log('called SearchSkillHandler.handle');

    const params = {
        TableName: 'skillmap',
        KeyConditionExpression: 'skill = :skill',
        ExpressionAttributeValues: {
            ':skill': `${skillsNameValue}`
        }
    };

    let skillsPersonName = '';
    // 実際にDynamoDBからデータ取得するところについては、await設定＋promiseを設定
    await documentClient.query(params, (err, data) => {
        if (err) {
            console.log(JSON.stringify(err, null, 2));
        } else {
            console.log(JSON.stringify(data, null, 2));
            console.log('取得したもの name : ' + data.Items[0].name);
            console.log('取得したもの skill : ' + data.Items[0].skill);
            skillsPersonName = data.Items[0].name;
            console.log('取得したもの skillsPersonName1 : ' + skillsPersonName);
        }
    }).promise();

    console.log('取得したもの skillsPersonName2 : ' + skillsPersonName);

    return handlerInput.responseBuilder
            .speak(`${skillsNameValue}` + "は" + `${skillsPersonName}` + "さんが詳しいです。")
            .getResponse();
  }
};

const skillBuilder = Alexa.SkillBuilders.custom();

// 使用するハンドラーをここで定義
exports.handler = skillBuilder
  .addRequestHandlers(
    GetNewFactHandler,
    SearchSkillHandler,
    HelloWorldIntentHandler,
    HelpHandler,
    ExitHandler,
    FallbackHandler,
    SessionEndedRequestHandler,
  )
  .addRequestInterceptors(LocalizationInterceptor)
  .addErrorHandlers(ErrorHandler)
  .withCustomUserAgent('sample/basic-fact/v2')
  .lambda();

const jpData = {
  translation: {
    SKILL_NAME: 'テスト',
  },
};

const languageStrings = {
  'ja': jpData,
};
```