Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Deep Dive Development Alexa Skill by Node.js

Deep Dive Development Alexa Skill by Node.js

Alexa day 2019

Hidetaka Okamoto

April 06, 2019
Tweet

More Decks by Hidetaka Okamoto

Other Decks in Programming

Transcript

  1. D e e p D i v e D e v e l o p m e n t
    A l e x a S k i l l b y N o d e . j s
    A l e x a D a y 2 0 1 9
    #alexaday2019

    View full-size slide

  2. L e a r n a b o u t …
    • ASK SDK
    • Developing Alexa Skills by TypeScript
    • Difference of S3 / DynamoDB
    • Testing & Deployment
    #alexaday2019

    View full-size slide

  3. H i d e t a k a
    O k a m o t o
    • Digitalcube Co. Ltd.
    • Alexa Campions
    • AWS Samurai 2017 in Japan

    View full-size slide

  4. E c h o ΁ ͷ ൃ ࿩ ͕ L a m b d a ʹ ಧ ͘ · Ͱ
    #alexaday2019

    View full-size slide

  5. Ի ੠ - > จ ࣈ - > J S O N ͷ 2 ε ς ο ϓ
    • ASR (Auto Speech Recognition)ͱNLU (Natural Language Understanging)
    • Echo͕ฉ͖औͬͨԻ੠ΛASR͕จࣈʹม׵͢Δ
    • ม׵͞ΕͨจࣈྻΛNLU͕JSONʹม׵͢Δ
    • Lambda͸JSONΛड͚औΓɺJSONΛฦ͚ͩ͢
    #alexaday2019

    View full-size slide

  6. ฉ ͖ औ Γ ɾ ೝ ࣝ ෦ ෼ ͷ ϙΠ ϯ τ
    • NLU͕JSON΁ͷม׵ʹʮର࿩ϞσϧʯΛར༻͢Δ
    • ςΩετΛਖ਼͍͠JSONʹม׵Ͱ͖͍ͯΔ͔ -> ର࿩Ϟσϧ
    • ड͚औͬͨJSONΛਖ਼͘͠ॲཧͰ͖͍ͯΔ͔ -> Lambdaͷ࣮૷
    • ʮೝࣝͰ͖ͯͳ͍ʯͱʮॲཧͰ͖ͯͳ͍ʯͷ੾Γ෼͚Λ
    #alexaday2019

    View full-size slide

  7. A S K S D K ͕ ΍ ͬͯ ͍ Δ ͜ ͱ
    #alexaday2019

    View full-size slide

  8. Ұ ൠ త ͳ ॻ ͖ ํ
    export handler = Ask.SkillBuilders.custom()
    .addRequestHandlers(...)
    .lambda()
    #alexaday2019

    View full-size slide

  9. L a m b d a ϥ ΠΫ ʹ ॻ ͖ ௚ ͢ͱ ͜ ͏ ͳ Δ
    export handler = Ask.SkillBuilders.custom()
    .addRequestHandlers(...)
    .lambda()
    export const handler= async (event) => {
    return Ask.SkillBuilders.custom()
    .addRequestHandlers(...)
    .create().invoke(event)
    }
    #alexaday2019

    View full-size slide

  10. l a m b d a ( ) ͕ ಺ ෦ Ͱ . c re a t e ( ) . i n v o k e ( e v e n t ) Λ ࣮ ߦ
    export handler = Ask.SkillBuilders.custom()
    .addRequestHandlers(...)
    .lambda()
    export const handler= async (event) => {
    return Ask.SkillBuilders.custom()
    .addRequestHandlers(...)
    .create().invoke(event)
    }
    #alexaday2019

    View full-size slide

  11. i n v o k e ( ) ಺ Ͱ e v e n t Λ h a n d l e r I n p u t ʹ ม ׵
    const input : HandlerInput = {
    requestEnvelope, //͜Ε͕event
    context,
    attributesManager : AttributesManagerFactory.init({
    requestEnvelope,
    persistenceAdapter : this.persistenceAdapter,
    }),
    responseBuilder : ResponseFactory.init(),
    serviceClientFactory : this.apiClient
    ? new ServiceClientFactory({
    apiClient : this.apiClient,
    apiEndpoint : requestEnvelope.context.System.apiEndpoint,
    authorizationValue : requestEnvelope.context.System.apiAccessToken,
    })
    : undefined,
    };
    https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/blob/2.0.x/ask-sdk-core/lib/skill/CustomSkill.ts#L65
    #alexaday2019

    View full-size slide

  12. h a n d l e r I n p u t ͷ த ਎ ֓ આ
    handerInput: {
    requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event),
    context: LambdaͷίϯςΩετ(=context),
    attributesManager: attributeૢ࡞ܥʹ͔ͭ͏,
    responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏,
    serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏
    }
    #alexaday2019

    View full-size slide

  13. Ϧ Ϋ Τ ε τ ಺ ༰ ( I n t e n t / S l o t / e t c . . ) Λ औ ಘ ͢ Δ
    handerInput: {
    requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event),
    context: LambdaͷίϯςΩετ(=context),
    attributesManager: attributeૢ࡞ܥʹ͔ͭ͏,
    responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏,
    serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏
    }
    #alexaday2019

    View full-size slide

  14. L a m b d a ͷ ֤ छ ৘ ใ ͕ ೖ ͬͯ ͍ Δ
    handerInput: {
    requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event),
    context: LambdaͷίϯςΩετ(=context),
    attributesManager: attributeૢ࡞ܥʹ͔ͭ͏,
    responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏,
    serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏
    }
    #alexaday2019

    View full-size slide

  15. η ο γ ϣ ϯ ΍ D B σ ʔ λ ͳ Ͳ ͷ ॲ ཧ ʹ ࢖ ͏
    handerInput: {
    requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event),
    context: LambdaͷίϯςΩετ(=context),
    attributesManager: attributeૢ࡞ܥʹ͔ͭ͏,
    responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏,
    serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏
    }
    #alexaday2019

    View full-size slide

  16. ฦ ౴ ಺ ༰ ͷ ࡞ ੒ ʹ ࢖ ͏
    handerInput: {
    requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event),
    context: LambdaͷίϯςΩετ(=context),
    attributesManager: attributeૢ࡞ܥʹ͔ͭ͏,
    responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏,
    serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏
    }
    #alexaday2019

    View full-size slide

  17. C u s t o m e r P ro f i l e A P I ͳ Ͳ ͷ ઀ ଓ ʹ ࢖ ͏
    handerInput: {
    requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event),
    context: LambdaͷίϯςΩετ(=context),
    attributesManager: attributeૢ࡞ܥʹ͔ͭ͏,
    responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏,
    serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏
    }
    #alexaday2019

    View full-size slide

  18. ॏ ཁ ౓ : ੺ ৭ > ࠇ ৭ > փ ৭
    handerInput: {
    requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event),
    context: LambdaͷίϯςΩετ(=context),
    attributesManager: attributeૢ࡞ܥʹ͔ͭ͏,
    responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏,
    serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏
    }
    #alexaday2019

    View full-size slide

  19. A l e x a ͷ Ϧ Ϋ Τ ε τ Λ ॲ ཧ ͢ Δ
    #alexaday2019

    View full-size slide

  20. ͢ ͝ ͍ J S O N
    {
    "version": "1.0",
    "session": {
    "new": true,
    "sessionId": "amzn1.echo-api.session.[unique-value-here]",
    "application": {
    "applicationId": "amzn1.ask.skill.[unique-value-here]"
    },
    "attributes": {
    "key": "string value"
    },
    "user": {
    "userId": "amzn1.ask.account.[unique-value-here]",
    "accessToken": "Atza|AAAAAAAA...",
    "permissions": {
    "consentToken": "ZZZZZZZ..."
    }
    }
    },
    "context": {
    "System": {
    "device": {
    "deviceId": "string",
    "supportedInterfaces": {
    "AudioPlayer": {}
    }
    },
    "application": {
    "applicationId": "amzn1.ask.skill.[unique-value-here]"
    },
    "user": {
    "userId": "amzn1.ask.account.[unique-value-here]",
    "accessToken": "Atza|AAAAAAAA...",
    "permissions": {
    "consentToken": "ZZZZZZZ..."
    }
    },
    "apiEndpoint": "https://api.amazonalexa.com",
    "apiAccessToken": "AxThk..."
    },
    "AudioPlayer": {
    "playerActivity": "PLAYING",
    "token": "audioplayer-token",
    "offsetInMilliseconds": 0
    }
    },
    "request": {}
    }
    https://developer.amazon.com/ja/docs/custom-skills/request-and-response-json-reference.html
    #alexaday2019

    View full-size slide

  21. ֮ ͑Β Ε ͳ ͍
    #alexaday2019

    View full-size slide

  22. A l e x a ͷ Ϧ Ϋ Τ ε τ Λ ॲ ཧ ͢ Δ
    ɹ ɹ ɹ ɹ ɹ ͨ Ί ͷ ੩ త ܕ ෇ ͖ ݴ ޠ ೖ ໳
    #alexaday2019

    View full-size slide

  23. #alexaday2019

    View full-size slide

  24. A l e x a Ͱ ͸ ͡ Ί Δ ੩ త ܕ ෇ ͚
    • Alexaͷ৔߹ɺInput / OutputͷϑΥʔϚοτ͕΄΅ݻఆ
    • ܕఆٛϑΝΠϧ΋ެ։͞Ε͍ͯΔͷͰɺ͋Δ΋ͷΛ࢖͑͹΄΅OK
    • IDEͰೖྗิ׬΍όϦσʔγϣϯ͕ೖΔͷͰɺϛε༧๷ʹͳΔ
    • ॳΊͯ࢖͏஋ɾϝιου΋ܕͷώϯτΛཔΓʹ࣮૷Ͱ͖Δ
    #alexaday2019

    View full-size slide

  25. ↑ ೖ ྗ ิ ׬ ͷ ྫ
    #alexaday2019

    View full-size slide

  26. ↓ ೖ ྗ ஋ ͷ ν Σ ο Ϋ
    #alexaday2019

    View full-size slide

  27. ܕ ৘ ใ ͸ 3 ݴ ޠ Ͱ ެ ։ ͞ Εͯ ͍ Δ
    • Python: https://github.com/alexa/alexa-apis-for-python
    • Java: https://github.com/alexa/alexa-apis-for-java
    • TypeScript: https://github.com/alexa/alexa-apis-for-nodejs
    #alexaday2019

    View full-size slide

  28. Ty p e S c r i p t ͷ η ο τΞ ο ϓ
    // install
    $ npm install -D typescript ask-sdk ask-sdk-model
    // setup
    $ ./node_modules/.bin/tsc --init
    // build
    $ ./node_modules/.bin/tsc
    #alexaday2019

    View full-size slide

  29. Ty p e S c r i p t ͷ η ο τΞ ο ϓ
    // build
    $ ./node_modules/.bin/tsc
    // package.jsonʹҎԼΛ଍͓ͯ͘͠ͱɺ
    // `npm run build`ͰϏϧυͰ͖Δ
    "scripts": {
    "build": "tsc"
    }
    #alexaday2019

    View full-size slide

  30. A S K C L I Ͱ ͷ σ ϓ ϩ Π
    • ࣄલʹ.ts -> .jsͷม׵͕ඞཁ
    • hooks/pre_deploy_hook.shΛ׆༻͢Δͱɺ

    ask deployͰϏϧυ -> σϓϩΠΛ࣮ߦՄೳ
    • npm prune —production΋ೖΕΔ͜ͱͰɺdevআ֎
    #alexaday2019

    View full-size slide

  31. / h o o k s / p re _ d e p l o y _ h o o k . s h
    install_dependencies() {
    npm install --prefix "$1" >/dev/null 2>&1
    # Լͷ3ߦΛ௥Ճ͢Δ
    npm run build --prefix "$1"
    rm -rf "$1/node_modules" >/dev/null 2>&1
    npm install --prefix "$1" --only=production >/dev/null 2>&1
    return $?
    }
    #alexaday2019

    View full-size slide

  32. G e t t i n g s t a r t e d Ty p e S c r i p t
    • null / undefinedͷߟྀ࿙Ε༧๷͚ͩͰ΋ϝϦοτେ
    • ೖྗิ׬Λ׆͔ͭͭ͠ɺϛεͷ༧๷ʹͭͳ͛Δ
    • ؔ਺ɾҾ਺͢΂ͯܕ৘ใ͕͋Γɺ৽ػೳΛ௥͍΍͍͢
    • gulp / webpackͳͲ͕࢖͑ΔͱɺΑΓศརʹͳΔ
    #alexaday2019

    View full-size slide

  33. S k i l l B u i l d e r s Ͳ ͬ ͪ Λ ࢖ ͏ ͔ ໰ ୊
    #alexaday2019

    View full-size slide

  34. s t a n d a rd / c u s t o m Λ બ ΂ Δ
    export handler = Ask.SkillBuilders
    .custom()
    .addRequestHandlers(...)
    .lambda()
    export handler = Ask.SkillBuilders
    .standard()
    .addRequestHandlers(...)
    .lambda()
    #alexaday2019

    View full-size slide

  35. ΄ ΅ ಉ ͡ ಈ ͖ Λ ͢ Δ ί ʔ υ
    import * as Ask from 'ask-sdk'
    Ask.SkillBuilders.standard()
    .addRequestHandlers(catchAll)
    .withTableName('tableName')
    .withAutoCreateTable(true)
    .lambda()
    import * as Ask from 'ask-sdk-core'
    Ask.SkillBuilders.custom()
    .addRequestHandlers(catchAll)
    .withApiClient(new Ask.DefaultApiClient())
    .withPersistenceAdapter(new
    DynamoDbPersistenceAdapter({
    tableName: 'tableName',
    createTable: true
    }))
    .lambda()
    S TA N D A R D C U S T O M
    #alexaday2019

    View full-size slide

  36. S e r v i c e C l i e n t ͷ ઃ ఆ ෦ ෼
    import * as Ask from 'ask-sdk'
    Ask.SkillBuilders.standard()
    .addRequestHandlers(catchAll)
    .withTableName('tableName')
    .withAutoCreateTable(true)
    .lambda()
    import * as Ask from 'ask-sdk-core'
    Ask.SkillBuilders.custom()
    .addRequestHandlers(catchAll)
    .withApiClient(new
    Ask.DefaultApiClient())
    .withPersistenceAdapter(new
    DynamoDbPersistenceAdapter({
    tableName: 'tableName',
    createTable: true
    }))
    .lambda()
    S TA N D A R D C U S T O M
    #alexaday2019

    View full-size slide

  37. D y n a m o D B ͷ ઃ ఆ ෦ ෼
    import * as Ask from 'ask-sdk'
    Ask.SkillBuilders.standard()
    .addRequestHandlers(catchAll)
    .withTableName('tableName')
    .withAutoCreateTable(true)
    .lambda()
    import * as Ask from 'ask-sdk-core'
    Ask.SkillBuilders.custom()
    .addRequestHandlers(catchAll)
    .withApiClient(new Ask.DefaultApiClient())
    .withPersistenceAdapter(new
    DynamoDbPersistenceAdapter({
    tableName: 'tableName',
    createTable: true
    }))
    .lambda()
    S TA N D A R D C U S T O M
    #alexaday2019

    View full-size slide

  38. S t a n d a rd o r C u s t o m
    • Standard͸DynamoDBσϑΥϧτ
    • Hosted Skillͷ৔߹ɺDBೖΕΔͳΒCustomҰ୒
    • ServiceClient / PersistantAttributesҎ֎͸΄΅ಉ͡
    • StandardͰεϞʔϧελʔτ -> CustomҠߦ΋Մೳ
    #alexaday2019

    View full-size slide

  39. ͬ͟ ͘ Γ Ϣ ʔεέ ʔε
    Case Standard / Custom
    ɹDatabaseΛ؆୯ʹಋೖ͍ͨ͠ Standard
    ɹCustomer ProfileͳͲͷApiΛ࢖͍͍ͨ Standard
    ɹHosted SkillͰDatabase࢖͍͍ͨ Custom
    ɹS3ΛDatabaseʹ͍ͨ͠ Custom
    ɹ͍Ζ͍ΖઃఆΛΧελϜ͍ͨ͠ Custom
    #alexaday2019

    View full-size slide

  40. D B Ͳ ͬ ͪ Λ ࢖ ͏ ͔ ໰ ୊
    #alexaday2019

    View full-size slide

  41. D y n a m o D B o r S 3
    • σϑΥϧτ͸DynamoDB
    • ͲͪΒ΋User IDΛΩʔʹɺObjectΛอଘ͢Δܗ
    • SLA͸΄΅ޡࠩ(S3: 99.9% / DynamoDB: 99.99%)
    • ແྉ࿮͕ແظݶͳͷ͸DynamoDB( S3͸12ϲ݄ͷΈ )
    • ैྔ՝ۚͷ৳ͼํ͸S3ͷ΄͏͕Ժ΍͔( 0.023USD / GB)
    #alexaday2019

    View full-size slide

  42. D y n a m o D B ͷ p ro s / c o n s
    • [cons] શ݅औಘ͸͋·Γ͓͢͢Ί͠ͳ͍ʢྉۚɾύϑΥʔϚϯεతʹʣ
    • [cons] MapܕͰσʔλΛೖΕΔͷͰɺΫΤϦ͠ਏ͍
    • [cons] ແྉ࿮Λ௒͑ͨ৔߹ʹS3ΑΓߴ͘ͳΓ΍͍͢
    • [props] standardΛ࢖͑͹؆୯ʹηοτΞοϓͰ͖Δ
    • [props] ੔߹ੑ͕औΓ΍͍͢
    • [props] ςʔϒϧ࡞੒΋SDK͕΍ͬͯ͘ΕΔ
    • [props] ແྉ࿮͕߃ৗ
    #alexaday2019

    View full-size slide

  43. S 3 ͷ p ro s / c o n s
    • [cons] ͦ΋ͦ΋DBͰ͸ͳ͍ͷͰɺ੔߹ੑͳͲʹଥڠ͕ඞཁ
    • [cons] ແྉ࿮͕12ϲ݄ݶఆ
    • [cons] Adapterͷઃఆ͕ඞཁ
    • [cons] όέοτΛखಈͰ࡞Δඞཁ͋Γ
    • [props] GB୯Ґͷ՝ۚͳͷͰɺແྉʹ͍ۙ
    • [props] HostedͰ࢖͑Δ
    • [props] Athena / S3 SelectͳͲͰσʔλΛݕࡧ͠΍͍͢
    #alexaday2019

    View full-size slide

  44. S D K ͷ ࢖ ͍ ํ ͩ ͱ D y n a m o D B
    ɹ ɹ ɹ ɹ ɹ ׆ ༻ ͠ ͖ Εͯ ͳ ͍ આ
    #alexaday2019

    View full-size slide

  45. S D K ͸ M a p ܕ Ͱ શ ෦ 1 Ω ʔ ʹ ͍ Εͯ ͠ · ͏
    dynamoDBDocumentClient.put({
    TableName: 'TableName',
    Item: {
    [partitionKey]: attributesId,
    [attributesName]: attributes
    }
    }).promise()
    A S K - S D K AW S - S D K
    #alexaday2019

    View full-size slide

  46. Ϋ Τ Ϧ ͱ ͔ ͠ ͨ ͘ ͳ Δ ͱ ɺ ͜ ͏ ͠ ͨ ͍
    dynamoDBDocumentClient.put({
    TableName: 'TableName',
    Item: {
    [partitionKey]: attributesId,
    [attributesName]: attributes
    }
    }).promise()
    dynamoDBDocumentClient.put({
    TableName: 'TableName',
    Item: {
    user_Id: attributesId,
    last_launched: attributes.last_launched,
    monthly_score: attributes.monthly_score,
    reminderTarget: attributes.reminder_target
    }
    }).promise()
    A S K - S D K AW S - S D K
    #alexaday2019

    View full-size slide

  47. O R Ͱ ͸ ͳ ͘ A N D ͱ ͍ ͏ ߟ ͑ ํ
    • શ݅औಘ͕ۤखͳDynamoDB
    • ࡉ͔͍ΫΤϦ΍੔߹ੑͷ֬อ͕ۤखͳS3
    • AWS-SDKͱڞʹซ༻ͯۤ͠ख෼໺ΛΧόʔ͢Δ
    • ྫɿ࣌ܥྻͷϩάσʔλ͚ͩS3ʹྲྀ͠ࠐΉ
    • ྫɿRead͚ͩසൟͳσʔλ͸S3ɾWrite͕ଟ͍σʔλ͸DynamoDB
    #alexaday2019

    View full-size slide

  48. https://speakerdeck.com/k1low/php-conference-2017?slide=50
    A p p e n d i x : ࣌ ܥ ྻ σ ʔ λ ͷ Ұ ཡ ͷ ࡞ Γ ํ

    View full-size slide

  49. ΄ ΅ ಉ ͡ ಈ ͖ Λ ͢ Δ ί ʔ υ Λ S 3 A d a p t e r Ͱ
    import * as moment from ‘moment'

    .withPersistenceAdapter(new S3PersistenceAdapter({
    bucketName: process.env.S3_PERSISTENCE_BUCKET,,
    pathPrefix: ObjectKeyGenerators.userId(event),
    objectKeyGenerator: () => {
    const timestamp = moment(moment().format('YYYY-
    MM-DD')).unix()
    return String((Math.pow(2, 53) - 1) - timestamp)
    }
    }))
    https://speakerdeck.com/k1low/php-conference-2017?slide=51

    View full-size slide

  50. p a t h P re f i x Λ u s e r I D ʹ ͯ͠ɺ σΟ Ϩ Ϋ τ Ϧ ෼ ׂ
    import * as moment from ‘moment'

    .withPersistenceAdapter(new S3PersistenceAdapter({
    bucketName: process.env.S3_PERSISTENCE_BUCKET,,
    pathPrefix: ObjectKeyGenerators.userId(event),
    objectKeyGenerator: () => {
    const timestamp = moment(moment().format('YYYY-
    MM-DD')).unix()
    return String((Math.pow(2, 53) - 1) - timestamp)
    }
    }))
    https://speakerdeck.com/k1low/php-conference-2017?slide=51

    View full-size slide

  51. ϑ Ν Πϧ ໊ Λ R e v e r s e d T i m e s t a m p I D ʹ ͯ͠ ι ʔ τ ͞ ͤ Δ
    import * as moment from ‘moment'

    .withPersistenceAdapter(new S3PersistenceAdapter({
    bucketName: process.env.S3_PERSISTENCE_BUCKET,,
    pathPrefix: ObjectKeyGenerators.userId(event),
    objectKeyGenerator: () => {
    const timestamp =
    moment(moment().format('YYYY-MM-DD')).unix()
    return String((Math.pow(2, 53) - 1) - timestamp)
    }
    }))
    https://speakerdeck.com/k1low/php-conference-2017?slide=51

    View full-size slide

  52. g e t Ͱ ͖ ͳ ͘ ͳ Δ ͷ Ͱɺ t i m e s t a m p ͸ ೥ ݄ ೔ · Ͱ ͕ ແ ೉
    import * as moment from ‘moment'

    .withPersistenceAdapter(new S3PersistenceAdapter({
    bucketName: process.env.S3_PERSISTENCE_BUCKET,,
    pathPrefix: ObjectKeyGenerators.userId(event),
    objectKeyGenerator: () => {
    const timestamp = moment(moment().format('YYYY-
    MM-DD')).unix()
    return String((Math.pow(2, 53) - 1) - timestamp)
    }
    }))
    https://speakerdeck.com/k1low/php-conference-2017?slide=51

    View full-size slide

  53. R e q u e s t H a n d l e r Λ ָ ʹ ॻ ͘
    #alexaday2019

    View full-size slide

  54. I n t e n t R e q u e s t ͸ ল ུ ه ๏ ͕ ͋ Δ
    skill.addRequestHandlers({
    canHandle(handlerInput) {
    return
    handlerInput.requestEnvelope.request.type ===
    'IntentRequest' &&
    handlerInput.requestEnvelope.request.intent.name
    === 'testIntent'
    },
    handle(handlerInput) {
    return handlerInput.responseBuilder
    .speak('test')
    .getResponse()
    }
    })
    a d d R e q u e s t H a n d l e r s a d d R e q u e s t H a n d l e r
    skill.addRequestHandler(
    ‘testIntent',
    (handlerInput)=> {
    return handlerInput.responseBuilder
    .speak('test')
    .getResponse()
    }
    )
    #alexaday2019

    View full-size slide

  55. I n t e n t R e q u e s t ͸ ল ུ ه ๏ ͕ ͋ Δ
    addRequestHandler(
    matcher : ((input : HandlerInput) => Promise | boolean) |
    string,
    executor : (input : HandlerInput) => Promise | Response,
    ) : BaseSkillBuilder {
    const canHandle = typeof matcher === 'string'
    ? ({ requestEnvelope } : HandlerInput) => {
    return matcher === (requestEnvelope.request.type ===
    'IntentRequest'
    ? (requestEnvelope.request as IntentRequest).intent.name
    : requestEnvelope.request.type);
    }
    : matcher;
    runtimeConfigurationBuilder.addRequestHandler(canHandle,
    executor);
    return this;
    },
    • addRequestHandler
    • ୈҰҾ਺͸จࣈྻorؔ਺
    • จࣈྻΛ౉͢ͱɺ
    IntentRequestѻ͍
    #alexaday2019

    View full-size slide

  56. ෳ ਺ ొ ࿥ ͠ ͩ ͢ͱ
    Χ Φ ε ʹ ͳΔ ͷ Ͱ ஫ ҙ
    3 Π ϯ ς ϯ τ ଍ ͢ ͩ ͚ Ͱ ͜͏ ͳΔ - >
    #alexaday2019

    View full-size slide

  57. Ty p e S c r i p t ͰΑ Γ ҆ શ ͳ
    ɹ R e q u e s t H a n d l e r Λ ࡞ Δ
    #alexaday2019

    View full-size slide

  58. Ty p e S c r i p t Ͱ ͸ ɺ ͜ Ε ͕ ܕ Τ ϥ ʔ ʹ ͳ Δ
    .addRequestHandler('ExampleIntent', async (handlerInput) => {
    const testSlot = handlerInput.requestEnvelope.request.intent.slots.hoge.value
    return handlerInput.responseBuilder.speak(`You choose ${testSlot}`).getResponse()
    })
    #alexaday2019

    View full-size slide

  59. h a n d l e r I n p u t . ~ . re q u e s t ͷ ܕ ͕ ൚ ༻ త ʹ ͳ ͬͯ ͍ Δ ͨ Ί
    • PlaybackFinishedRequest
    • SkillEnabledRequest
    • ListUpdatedEventRequest
    • LaunchRequest
    • IntentRequest
    • ProactiveSubscriptionChangedRequest
    • UserEvent
    • SkillDisabledRequest
    • ElementSelectedRequest
    • PermissionChangedRequest
    • ListItemsCreatedEventRequest
    • etc…
    #alexaday2019

    View full-size slide

  60. G e n e r i c s Λ ࢖ ͬͯ ܕ Λ ্ ॻ ͖ Ͱ ͖ Δ
    import { RequestHandler } from 'ask-sdk-core';
    export const testHandler2: RequestHandler = {
    canHandle(handlerInput) {
    return true
    },
    handle(handlerInput) {
    return handlerInput.responseBuilder
    .speak('test')
    .getResponse()
    }
    }
    import { RequestHandler } from 'ask-sdk-runtime';
    import { Response } from 'ask-sdk-model';
    import { HandlerInput } from 'ask-sdk-core';
    export const testHandler: RequestHandlerResponse> = {
    canHandle(handlerInput) {
    return true
    },
    handle(handlerInput) {
    return handlerInput.responseBuilder
    .speak('test')
    .getResponse()
    }
    }
    ௨ ৗ ্ ॻ ͖
    #alexaday2019

    View full-size slide

  61. ࣗ લ Ͱ i n t e r f a c e Λ ࡞ ͬͯ ͠ · ͏ ͱ Α Γ ศ ར
    import { RequestHandler } from 'ask-sdk-runtime';
    import { Response, RequestEnvelope, Request } from 'ask-sdk-model';
    import { HandlerInput } from ‘ask-sdk-core';
    // handlerInput / requestEnvelopeͷinterfaceΛܧঝ͓ͤͯ͘͞
    export interface MyCustomeRequestEnvelope extends RequestEnvelope {
    request: CustomRequest
    }
    export interface MyCustomHandlerInput extends HandlerInput {
    requestEnvelope : MyCustomeRequestEnvelope
    }
    // requestͷܕΛ্ॻ͖Ͱ͖ΔΑ͏ʹͨ͠handlerͷInterface
    export interface MySkillRequestHandler extends RequestHandler {}
    #alexaday2019

    View full-size slide

  62. ࣗ લ Ͱ i n t e r f a c e Λ ࡞ ͬͯ ͠ · ͏ ͱ Α Γ ศ ར
    import { RequestHandler } from 'ask-sdk-runtime';
    import { Response, RequestEnvelope, Request } from 'ask-sdk-model';
    import { HandlerInput } from ‘ask-sdk-core';
    // handlerInput / requestEnvelopeͷinterfaceΛܧঝ͓ͤͯ͘͞
    export interface MyCustomeRequestEnvelope extends RequestEnvelope {
    request: CustomRequest
    }
    export interface MyCustomHandlerInput extends HandlerInput {
    requestEnvelope : MyCustomeRequestEnvelope
    }
    // requestͷܕΛ্ॻ͖Ͱ͖ΔΑ͏ʹͨ͠handlerͷInterface
    export interface MySkillRequestHandler extends RequestHandler {}
    #alexaday2019

    View full-size slide

  63. ࣗ લ Ͱ i n t e r f a c e Λ ࡞ ͬͯ ͠ · ͏ ͱ Α Γ ศ ར
    import { RequestHandler } from 'ask-sdk-runtime';
    import { Response, RequestEnvelope, Request } from 'ask-sdk-model';
    import { HandlerInput } from ‘ask-sdk-core';
    // handlerInput / requestEnvelopeͷinterfaceΛܧঝ͓ͤͯ͘͞
    export interface MyCustomeRequestEnvelope extends RequestEnvelope {
    request: CustomRequest
    }
    export interface MyCustomHandlerInput extends HandlerInput {
    requestEnvelope : MyCustomeRequestEnvelope
    }
    // requestͷܕΛ্ॻ͖Ͱ͖ΔΑ͏ʹͨ͠handlerͷInterface
    export interface MySkillRequestHandler
    extends RequestHandler {}
    #alexaday2019

    View full-size slide

  64. ͜ ͏ ͢ Δ ͱ I n t e n t R e q u e s t ͷ ͨ Ί ͷ h a n d l e r ʹ Ͱ ͖ Δ
    interface MyNewIntentHandlerInput extends MyCustomHandlerInput {}
    export const testHandler: MySkillRequestHandler = {
    canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
    },
    handle(handlerInput) {
    const { name } = handlerInput.requestEnvelope.request.intent
    return handlerInput.responseBuilder
    .speak(`You are request is ${name} Intent.`)
    .getResponse()
    }
    }
    #alexaday2019

    View full-size slide

  65. I n t e n t ͱ C o n t e x t ʢ ҙ ਤ ͱ จ ຺ ʣ
    #alexaday2019

    View full-size slide

  66. Ye s / N o I n t e n t ͳ Ͳ ͸ ɺ จ ຺ Ͱ ҙ ຯ ͕ ม Θ Δ
    • ʮͳʹʹର͢Δ͸͍ɾ͍͍͑ʯͳͷ͔
    • Session / Persistant AttributeͰจ຺ʢContextʣΛه࿥͢Δ
    • ContextʹԠͯ͡ॲཧͷ಺༰Λม͑Δඞཁ͕͋Δ
    #alexaday2019

    View full-size slide

  67. ಉ ͡ ҙ ਤ ͩ ͕ དྷ Δ Ϧ Ϋ Τ ε τ ͕ ม Θ Δ ྫ
    • ϢʔβʔʮAlexaɺXXͰYYͯ͠ʯ
    • AlexaʮΘ͔Γ·ͨ͠ɻʯ
    • ϢʔβʔʮAlexaɺXX։͍ͯʯ
    • AlexaʮXX΁Α͏ͦ͜ɻYY͠·͔͢ʁʯ
    • Ϣʔβʔʮ͸͍ʯ
    • AlexaʮΘ͔Γ·ͨ͠ʯ
    D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t
    #alexaday2019

    View full-size slide

  68. c a n H a n d l e Ҏ ֎ ΄ ΅ ಉ ͡ ʹ ͳ Γ ΍ ͢ ͍
    const DoSomethingIntent = {
    canHandle(handlerInput: HandlerInput) {
    return isDoSomethingIntent(handlerInput)
    }
    handle(handlerInput: HandlerInput) {
    return handlerInput.responseBuilder
    .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ')
    .getResponse()
    }
    }
    const DoSomethingYesIntent = {
    canHandle(handlerInput: HandlerInput) {
    return isYesIntent(handlerInput)
    }
    handle(handlerInput: HandlerInput) {
    return handlerInput.responseBuilder
    .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ')
    .getResponse()
    }
    }
    D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t
    #alexaday2019

    View full-size slide

  69. O b j e c t . a s s i g n Ͱ ஋ ౉ ͠ ͯ͠ ΍ Δ ͱ D RY ʹ ͳ Δ
    const DoSomethingIntent = {
    canHandle(handlerInput: HandlerInput) {
    return isDoSomethingIntent(handlerInput)
    }
    handle(handlerInput: HandlerInput) {
    return handlerInput.responseBuilder
    .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ')
    .getResponse()
    }
    }
    const DoSomethingYesIntent =
    Object.assign(
    {},
    DoSomethingIntent,
    {
    canHandle(handlerInput: HandlerInput)
    {
    return isYesIntent(handlerInput)
    }
    }
    )
    D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t
    #alexaday2019

    View full-size slide

  70. ܧ ঝ ͢ Δ h a n d l e r Λ ୈ ೋ Ҿ ਺ ΁
    const DoSomethingIntent = {
    canHandle(handlerInput: HandlerInput) {
    return isDoSomethingIntent(handlerInput)
    }
    handle(handlerInput: HandlerInput) {
    return handlerInput.responseBuilder
    .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ')
    .getResponse()
    }
    }
    const DoSomethingYesIntent =
    Object.assign(
    {},
    DoSomethingIntent,
    {
    canHandle(handlerInput: HandlerInput) {
    return isYesIntent(handlerInput)
    }
    }
    )
    D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t
    #alexaday2019

    View full-size slide

  71. ୈ ࡾ Ҿ ਺ ʹ ্ ॻ ͖ ͢ Δ ಺ ༰ Λ ͔ ͚ ͹ O K
    const DoSomethingIntent = {
    canHandle(handlerInput: HandlerInput) {
    return isDoSomethingIntent(handlerInput)
    }
    handle(handlerInput: HandlerInput) {
    return handlerInput.responseBuilder
    .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ')
    .getResponse()
    }
    }
    const DoSomethingYesIntent =
    Object.assign(
    {},
    DoSomethingIntent,
    {
    canHandle(handlerInput: HandlerInput)
    {
    return isYesIntent(handlerInput)
    }
    }
    )
    D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t
    #alexaday2019

    View full-size slide

  72. Te s t i n g y o u r S k i l l
    #alexaday2019

    View full-size slide

  73. A l e x a εΩϧ Ͱ ͷ ςε τ ͷ ߟ ͑ ํ ( ྫ )
    • API / DBͳͲɺςετ͕ཉ͍͠ن໛ͷεΩϧ͸ґଘ͕ڧ͘ͳΔ
    • RequestHandlerͷςετ͸݁߹ςετʹͳΓ΍͍͢
    • ॲཧ෦෼Λந৅Խ͠ɺͰ͖Δ͚ͩϢχοτςετͰ͖ΔΑ͏ʹ࡞Δ
    • UX෦෼͸ແཧʹςετίʔυΛॻ͜͏ͱ͠ͳ͍
    #alexaday2019

    View full-size slide

  74. ͨ ͱ ͑ ͹ ε ϩ ο τ ̎ ͭ Λ ॲ ཧ ͢ Δ ϋ ϯ υ ϥ ʔ
    const testHandler: Ask.RequestHandler = {
    canHandle(handlerInput) {…},
    handle(handlerInput) {
    const request = handlerInput.requestEnvelope.request as IntentRequest
    const name = request.intent.slots ? request.intent.slots.name.value : ''
    const number = request.intent.slots ? request.intent.slots.number.value : ''
    if (!name || !number) {
    return handlerInput.responseBuilder
    .speak(`you need to say name and number.`)
    .getResponse()
    }
    return handlerInput.responseBuilder
    .speak(`you say ${name} ${number}.`)
    .getResponse()
    }
    }
    #alexaday2019

    View full-size slide

  75. S l o t ͷ ॲ ཧ ෦ ෼ Λ ؔ ਺ ʹ ͢ Δ
    const testHandler: Ask.RequestHandler = {
    canHandle(handlerInput) {…},
    handle(handlerInput) {
    const request = handlerInput.requestEnvelope.request as IntentRequest
    const content = getSpeechContent(request)
    if (!content) {
    return handlerInput.responseBuilder
    .speak(`you need to say name and number.`)
    .getResponse()
    }
    return handlerInput.responseBuilder
    .speak(`you say ${content}.`)
    .getResponse()
    }
    }
    const getSpeechContent = (request:
    IntentRequest) => {
    const name = request.intent.slots ?
    request.intent.slots.name.value : ''
    const number = request.intent.slots ?
    request.intent.slots.number.value : ''
    if (!name || !number) return ''
    return `${name} ${number}.`
    }

    View full-size slide

  76. ݁ ߹ ςε τ : V i r t u a l A l e x a
    https://github.com/bespoken/virtual-alexa
    #alexaday2019

    View full-size slide

  77. N L U Ҏ ߱ ͷ ॲ ཧ Λ ϩ ʔ Χ ϧ Ͱ ςε τ Ͱ ͖ Δ
    it("Accepts responses without dollars", async function () {
    const alexa = bvd.VirtualAlexa.Builder()
    .handler("index.handler") // Lambda function file and name
    .intentSchemaFile("./speechAssets/IntentSchema.json") // Uses old-style intent schema
    .sampleUtterancesFile("./speechAssets/SampleUtterances.txt")
    .create();
    const launchResponse = await alexa.launch();
    expect(launchResponse.response.outputSpeech.ssml).toContain(‘test')
    const numberResponse = await alexa.utter('I think it is 1')
    expect(numberResponse.response.outputSpeech.ssml).toContain('you say 1’)
    const numberResponse2 = await alexa.intend('NumberGuessIntent', { number: "2"})
    expect(numberResponse2.response.outputSpeech.ssml).toContain('you say 2')
    });
    #alexaday2019

    View full-size slide

  78. D y n a m o D B ͱ Ұ ෦ A P I ͷ Ϟ ο Ϋ ͕ ར ༻ Մ ೳ ɿ 2 0 1 9 / 4 / 6 ࣌ ఺
    import { VirtualAlexa } from 'virtual-alexa';
    import { handler } from '../index'
    const alexa = VirtualAlexa.Builder()
    .handler(test)
    .interactionModelFile('../../models/en-
    US.json')
    .create()
    // DynamoDBͷϞοΫ
    alexa.dynamoDB().mock()
    virtualAlexa.addressAPI().returnsFullAddr
    ess({
    addressLine1: "address line 1",
    addressLine2: "address line 2",
    addressLine3: "address line 3",
    city: "city",
    countryCode: "country code",
    districtOrCounty: "district",
    postalCode: "postal",
    stateOrRegion: "state",
    });
    #alexaday2019

    View full-size slide

  79. a s k s i m u l a t e Ͱ σ ϓ ϩ Π ޙ ͷ ςε τ ΋ ( Ұ Ԡ ) Մ ೳ
    const { execFile } = require('child_process')
    describe('test by ask-cli', () => {
    it('should return valid response when send invocation name', (done) => {
    execFile('ask', [
    'simulate', '-s', 'amzn1.ask.skill.d7d6176c-ab76-4e4c-b5ee-81366c4cd223',
    '-l', 'en-US', '-t', 'open greeter'
    ], (error, stdout, stderr) => {
    if (error) {
    assert.deepEqual(error, {})
    } else {
    const { result } = JSON.parse(stdout)
    assert.deepEqual(result.skillExecutionInfo.invocationResponse.body.response, {
    card: {
    type: 'Simple',
    title: 'Hello World',
    content: 'Welcome to the Alexa Skills Kit, you can say hello!'
    },
    #alexaday2019

    View full-size slide

  80. β ςε τ ɾ Ϩ Ϗϡ ʔ ґ པ ͷ ͢ ͢Ί
    • ίʔυͷςετͰ͸ςετͰ͖ͳ͍΋ͷ͕͋Δ
    • ʮฦ౴͕ෆࣗવͰͳ͍͔ʁʯʮಡΈؒҧ͍͸ͳ͍͔ʁʯetc…
    • ࣮ࡍʹ࢖ͬͯΈͯΘ͔Δ͜ͱ΋ଟ͍
    • εΩϧΛ஌ͬͯ΋Β͏͜ͱ΋݉Ͷͯɺ஌Γ߹͍ʹ࢖ͬͯ΋Β͓͏
    • ެ։લͳΒЌςετʹট଴ɺެ։ޙͳΒϨϏϡʔΛॻ͍ͯ΋Β͏
    • ϨϏϡʔ͕૿͑Δͱ͍͍͜ͱ͕͋Δ͔΋
    #alexaday2019

    View full-size slide

  81. I n t e rc e p t o r Ͱ R e q u e s t / R e s p o n s e Λ ه ࿥
    const RequestLogger = {
    process (handlerInput: HandlerInput): void {
    console.log('RequestEnvelope: %j', handlerInput.requestEnvelope)
    }
    }
    const ResponseLogger ={
    process (handlerInput: HandlerInput, response: Response): void {
    console.log(`Response: ${JSON.stringify(response)}`)
    }
    }
    Ask.SkillBuilders.custom()
    .addRequestHandlers(
    ...
    )
    .addRequestInterceptors(
    RequestLogger
    )
    .addResponseInterceptors(
    ResponseLogger
    )
    .lambda()
    L o g g e r A D D
    #alexaday2019

    View full-size slide

  82. I n t e rc e p t o r Ͱ R e q u e s t / R e s p o n s e Λ ه ࿥
    • Request / Responseͷϩά͕͋Ε͹࠶ݱͱΓ΍͍͢
    • ͨͩ͠slot / sessionʹηϯγςΟϒͳ৘ใ͕ೖΔ͜ͱ͕͋Δ
    • ϓϥΠόγʔΛѻ͏εΩϧͰ͸ग़ྗલʹϚεΫΛ
    • Sentry / RollbarͳͲͷπʔϧͰΤϥʔ؂ࢹ͢Δͱ໰୊ʹؾ͖ͮ΍͍͢
    #alexaday2019

    View full-size slide

  83. D e p l o y m e n t
    #alexaday2019

    View full-size slide

  84. A l e x a S k i l l b a c k e n d ͷ σ ϓ ϩ Π ํ ๏ Ұ ྫ
    • ASK SDKͰask deploy
    • Code Star + Cloud9Ͱgit push
    • Serverless FrameworkͰsls deploy
    • AWS SAMͰdeploy
    #alexaday2019

    View full-size slide

  85. A l e x a S k i l l b a c k e n d ͷ σ ϓ ϩ Π ํ ๏ Ұ ྫ
    • ASK SDKͰask deploy
    • Code Star + Cloud9Ͱgit push
    • Serverless FrameworkͰsls deploy
    • AWS SAMͰdeploy
    #alexaday2019

    View full-size slide

  86. ͦ Ε ͧ ΕͰ ͷ ఆ ٛ ͷ ॻ ͖ ํ
    service:
    name: my-alexa-skill
    provider:
    name: aws
    runtime: nodejs8.10
    functions:
    alexa:
    handler: index.alexa
    events:
    - alexaSkill: amzn1.ask.skill.xxxxxx
    AWSTemplateFormatVersion: '2010-09-09'
    Transform: AWS::Serverless-2016-10-31
    Description: Example Alexa skill backend
    Resources:
    HelloAlexa:
    Type: AWS::Serverless::Function
    Properties:
    Handler: index.handler
    Runtime: nodejs8.10
    Events:
    Alexa:
    Type: AlexaSkill
    Outputs:
    HelloAlexaFunction:
    Description: "Hello Alexa Lambda Function ARN"
    Value: !GetAtt HelloAlexa.Arn
    S e r v e r l e s s F r a m e w o r k S A M
    #alexaday2019

    View full-size slide

  87. S A M Ͱ ͷ Ξ ο ϓ σ ʔ τ
    • ৽͍͠όʔδϣϯͱͯ͠σϓϩΠ -> ΤΠϦΞεࠩ͠ସ͑Ͱ൓ө
    • YAMLʹ਺ߦ଍͚ͩ͢ͰCodeDeploy͕ར༻Ͱ͖Δ
    • ஈ֊తʹτϥϑΟοΫΛৼΓ෼͚Δ͜ͱ͕Մೳ
    • CloudWatch AlarmͳͲͱ૊Έ߹ΘͤΕ͹ϩʔϧόοΫ΋
    • CodePiplineΛ࢖͑͹खಈঝೝͷϫʔΫϑϩʔ΋૊ΊΔ
    #alexaday2019

    View full-size slide

  88. S A M Ͱ Ұ ఆ ྔ Τ ϥ ʔ ͕ ग़ Δ ͳ Β ϩ ʔ ϧό ο Ϋ ͢ Δ α ϯ ϓϧ
    # ࠨͷଓ͖
    HelloAlexaAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
    Namespace: AWS/Lambda
    Dimensions:
    - Name: FunctionName
    Value: !Ref HelloAlexa
    MetricName: Errors
    ComparisonOperator: GreaterThanOrEqualToThreshold
    Statistic: Sum
    Period: 60
    EvaluationPeriods: 1
    Threshold: 1
    Outputs:
    HelloAlexaFunction:
    Description: "Hello Alexa Lambda Function ARN"
    Value: !GetAtt HelloAlexa.Arn
    AWSTemplateFormatVersion: '2010-09-09'
    Transform: AWS::Serverless-2016-10-31
    Description: Example Alexa skill backend
    Resources:
    HelloAlexa:
    Type: AWS::Serverless::Function
    Properties:
    Handler: index.handler
    Runtime: nodejs8.10
    AutoPublishAlias: live
    DeploymentPreference:
    Enabled: true
    Type: Linear10PercentEvery1Minute
    Alarms:
    - !Ref HelloAlexaAlarm

    View full-size slide

  89. C a n a r y D e p l o y ͷ ઃ ఆ
    # ࠨͷଓ͖
    HelloAlexaAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
    Namespace: AWS/Lambda
    Dimensions:
    - Name: FunctionName
    Value: !Ref HelloAlexa
    MetricName: Errors
    ComparisonOperator: GreaterThanOrEqualToThreshold
    Statistic: Sum
    Period: 60
    EvaluationPeriods: 1
    Threshold: 1
    Outputs:
    HelloAlexaFunction:
    Description: "Hello Alexa Lambda Function ARN"
    Value: !GetAtt HelloAlexa.Arn
    AWSTemplateFormatVersion: '2010-09-09'
    Transform: AWS::Serverless-2016-10-31
    Description: Example Alexa skill backend
    Resources:
    HelloAlexa:
    Type: AWS::Serverless::Function
    Properties:
    Handler: index.handler
    Runtime: nodejs8.10
    AutoPublishAlias: live
    DeploymentPreference:
    Enabled: true
    Type: Linear10PercentEvery1Minute
    Alarms:
    - !Ref HelloAlexaAlarm

    View full-size slide

  90. R o l l b a c k ͷ ઃ ఆ
    # ࠨͷଓ͖
    HelloAlexaAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
    Namespace: AWS/Lambda
    Dimensions:
    - Name: FunctionName
    Value: !Ref HelloAlexa
    MetricName: Errors
    ComparisonOperator: GreaterThanOrEqualToThreshold
    Statistic: Sum
    Period: 60
    EvaluationPeriods: 1
    Threshold: 1
    Outputs:
    HelloAlexaFunction:
    Description: "Hello Alexa Lambda Function ARN"
    Value: !GetAtt HelloAlexa.Arn
    AWSTemplateFormatVersion: '2010-09-09'
    Transform: AWS::Serverless-2016-10-31
    Description: Example Alexa skill backend
    Resources:
    HelloAlexa:
    Type: AWS::Serverless::Function
    Properties:
    Handler: index.handler
    Runtime: nodejs8.10
    AutoPublishAlias: live
    DeploymentPreference:
    Enabled: true
    Type: Linear10PercentEvery1Minute
    Alarms:
    - !Ref HelloAlexaAlarm

    View full-size slide

  91. S A M ͷ p ro s / c o n s
    • [pros] CloudFormationނʹࣗ༝౓͕ߴ͍
    • [pros] σϓϩΠ·ΘΓͷΦϓγϣϯ͕๛෋
    • [pros] CodeXX γϦʔζͱͷ૬ੑ͕ྑ͍
    • [pros] ϩʔΧϧ࣮ߦ༻ͷπʔϧ΋ެࣜͰ͋Δ
    • [cons] σϓϩΠίϚϯυ͕ෳࡶʹͳΓ΍͍͢
    • [cons] ϩάͷtailίϚϯυ͕ͳ͍
    • [cons] CloudFormationΛॻ͔ͳ͍ͱ͍͚ͳ͍

    View full-size slide

  92. S e r v e r l e s s F r a m e w o r k Ͱ ͷ Ξ ο ϓ σ ʔ τ
    • sls deployͰIAMͳͲͷϦιʔε͝ͱߋ৽Մೳ
    • sls deploy functionͰLambdaͷιʔεͷΈߋ৽Մೳ
    • σϑΥϧτͰ͸ΤΠϦΞε͕ͳ͍ͷͰɺ--stageΦϓγϣϯΛ࢖͏
    • ৽stage࡞੒ -> ΤϯυϙΠϯτࠩ͠ସ͑ -> ਃ੥ -> چstage ࡟আ
    • ϓϥάΠϯΛ࢖͑͹Alias΋ར༻Մೳ
    #alexaday2019

    View full-size slide

  93. S e r v e r l e s s F r a m e w o r k ͷ p ro s / c o n s
    • [pros] CloudFormationΑΓͬ͘͟Γॻ͚Δ[ಛʹIAM·ΘΓ]
    • [pros] ೔ຊޠͷ৘ใଟΊɾϑΥʔϥϜ͋Δ
    • [pros] CLIʹϩάͷtailίϚϯυ͕͋ͬͯσόοάָ͕
    • [pros] ର࿩Ϟσϧ؅ཧɾΦϑϥΠϯσόοά౳ͷϓϥάΠϯ͕๛෋
    • [cons] σϓϩΠʹखಈ෦෼͕࢒Δ
    • [cons] ϓϥάΠϯͷબఆΛϛεΔͱͭΒ͍
    • [cons] CloudFormationΛ݁ہॻ͘͜ͱʹͳΓ͔Ͷͳ͍

    View full-size slide

  94. ͦ Ε ͧ ΕͰ ͷ σ ϓ ϩ Π ί Ϛ ϯ υ
    S e r v e r l e s s F r a m e w o r k S A M
    #alexaday2019

    View full-size slide

  95. ͦ Ε ͧ ΕͰ ͷ σ ϓ ϩ Π ί Ϛ ϯ υ
    $ sls deploy $ aws cloudformation package \
    —template-file ./template.yml \
    —output-template-file template-output.yml
    \
    —s3-bucket $S3_BUCKET_NAME
    $ aws cloudformation deploy \
    --template-file ./template-output.yml \
    —stack-name helloAlexa \
    —capabilities CAPABILITY_IAM \
    —region $AWS_REGION
    S e r v e r l e s s F r a m e w o r k S A M
    #alexaday2019

    View full-size slide

  96. ݸ ਓ త ࢖ ͍ ෼ ͚
    • ͱΓ͋͑ͣ࡞ͬͯΈ͍ͨ: Hosted Skill
    • GitͰ؅ཧ͍ͨ͠: ASK CLI only
    • νʔϜͰ։ൃӡ༻͸͡Ί͍ͨɿCode Star
    • DBͱ͔࢖͍͍ͨ: Serverless Framework
    • ഁյతมߋΛఆظతʹ΍Γͦ͏ɿSAM
    • Ϣʔβʔ૿͑ͦ͏͔ͩΒ҆ఆӡ༻͍ͤͨ͞ɿSAM
    #alexaday2019

    View full-size slide

  97. A p p e n d i x : C o d e S t a r
    • AlexaεΩϧ։ൃͷͨΊʹඞཁͳϦιʔεΛ਺ΫϦοΫͰ༻ҙ
    • Cloud9Ͱϒϥ΢β্ͰͷνʔϜ։ൃ΋Մೳ
    • Code seriesͱ࿈ܞࡁͳͷͰɺσϓϩΠύΠϓϥΠϯ׬උ
    • ͨͩ͠Alexaͷ։ൃऀίϯιʔϧͰฤूͰ͖ͳ͘ͳΔ
    • Hosted SkillͷਅٯͷଘࡏʢAmazom or AWS͚ͩͰ؅ཧʣ
    #alexaday2019

    View full-size slide

  98. A S K S D K ΁ ͷ D e e p D i v e
    • IDEΛ࢖͑͹ؾʹͳΔϝιουɾύϥϝʔλͷܕఆٛΛ௥͑Δ
    • ϦΫΤετ஋ / ServiceClient͸ask-sdk-modelΛݟΔ
    • ֤छϝιου͸جຊతʹask-sdk-coreΛݟΔ
    • ίΞͷιʔεΛखݩʹclone͓ͯ͘͠ͱίʔυಡΈ͕ḿΔ
    #alexaday2019

    View full-size slide

  99. ઃ ܭ ཧ ղ ͸ Q i i t a ͷ ͜ ͷ ه ࣄ Λ
    https://qiita.com/shinichi-takahashi/items/7191d3d393e08b2746f0
    #alexaday2019

    View full-size slide

  100. ཧ ղ ͢ Δ ͜ ͱ Ͱ
    ΄ ͔ ΁ ͷ Ԡ ༻ ΋ Մ ೳ ʹ
    class Get {
    supports(method: string) {
    return method === 'GET'
    }
    handle(){
    return ResponseBuilder.setStatusCode(StatusCode || 200)
    .setBody({ message: 'hello' })
    .getResponse()
    }
    }
    export const handler: APIGatewayProxyHandler = async (event) => {
    event.body = event.body != null ? JSON.parse(event.body) : null
    const resolver = new Resolver(
    new Post(),
    new Get()
    )
    return resolver.resolve(event.httpMethod).handle(event)
    }
    A P I G a t e w a y ʹ Ԡ ༻ ͠ ͨ ྫ
    #alexaday2019

    View full-size slide

  101. ந ৅ Խ ͠ ͨ ί ʔ υ Λ
    ެ ։ ͠ Α ͏
    • ASK SDK޲͚Utility
    npm i -S ask-utils
    • Proactive Event޲͚SDK
    npm i -S @ask-utils/proactive-event
    • Amazon Pay޲͚Ϗϧμʔ
    npm i -S @ask-utils/amazon-pay
    #alexaday2019

    View full-size slide

  102. C o n c l u s i o n
    • ASK SDKΛཧղ͢Δ͜ͱͰɺޮ཰తͳ࣮૷͕ՄೳʹͳΔ
    • TypeScriptͳͲͷ੩తܕ෇͖ݴޠͰIDEͷԸܙΛ͏͚Δ
    • S3 / DynamoDB / and moreͷ͍͍ͱ͜औΓΛΊͦ͟͏
    • ςετͱσϓϩΠΛ҆ఆԽͤͯ͞ɺεΩϧΛΑΓศརʹ
    #alexaday2019

    View full-size slide

  103. A p p e n d i x : ࣮ ͸ ͜ Μ ͳ ؔ ਺ ͋ Γ · ͢
    import * as Ask from ‘ask-sdk-core'
    const client = new Ask.DefaultApiClient()
    const result = await client.invoke({
    method: 'GET',
    url: 'https://example.com',
    headers: []
    })
    import * as Ask from ‘ask-sdk-core’
    // ϦΫΤετλΠϓ
    Ask.getRequestType(requestEnvelope)
    LaunchRequest
    // Πϯςϯτ໊
    Ask.getIntentName(requestEnvelope)
    AMAZON.YesIntent
    // ৽͍͠ηογϣϯ͔൱͔Λ൑ఆ
    Ask.isNewSession(requestEnvelope)
    true
    ֎ ෦ ΁ ͷ F e t c h ॲ ཧ Ϧ Ϋ Τ ε τ ͷ ஋ Λ औ Γ ग़ ͢ ϔ ϧ ύ ʔ
    #alexaday2019

    View full-size slide