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
PRO

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 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 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 Slide

  4. View Slide

  5. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  11. 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 Slide

  12. 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 Slide

  13. 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 Slide

  14. 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 Slide

  15. Ϧ Ϋ Τ ε τ ಺ ༰ ( 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

  19. 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 Slide

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

    View Slide

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

    View Slide

  22. ͢ ͝ ͍ 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 Slide

  23. ֮ ͑Β Ε ͳ ͍
    #alexaday2019

    View Slide

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

    View Slide

  25. #alexaday2019

    View Slide

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

    View Slide

  27. ↑ ೖ ྗ ิ ׬ ͷ ྫ
    #alexaday2019

    View Slide

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

    View Slide

  29. ܕ ৘ ใ ͸ 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 Slide

  30. 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 Slide

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

    View Slide

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

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

    View Slide

  33. / 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 Slide

  34. 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 Slide

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

    View Slide

  36. 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 Slide

  37. ΄ ΅ ಉ ͡ ಈ ͖ Λ ͢ Δ ί ʔ υ
    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 Slide

  38. 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 Slide

  39. 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 Slide

  40. 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 Slide

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

    View Slide

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

    View Slide

  43. 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 Slide

  44. 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 Slide

  45. 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 Slide

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

    View Slide

  47. 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 Slide

  48. Ϋ Τ Ϧ ͱ ͔ ͠ ͨ ͘ ͳ Δ ͱ ɺ ͜ ͏ ͠ ͨ ͍
    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 Slide

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

    View Slide

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

    View Slide

  51. ΄ ΅ ಉ ͡ ಈ ͖ Λ ͢ Δ ί ʔ υ Λ 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 Slide

  52. 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 Slide

  53. ϑ Ν Πϧ ໊ Λ 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 Slide

  54. 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 Slide

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

    View Slide

  56. 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 Slide

  57. 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 Slide

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

    View Slide

  59. 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 Slide

  60. 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 Slide

  61. 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 Slide

  62. 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 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 Slide

  64. ࣗ લ Ͱ 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 Slide

  65. ࣗ લ Ͱ 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 Slide

  66. ͜ ͏ ͢ Δ ͱ 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 Slide

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

    View Slide

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

    View Slide

  69. ಉ ͡ ҙ ਤ ͩ ͕ དྷ Δ Ϧ Ϋ Τ ε τ ͕ ม Θ Δ ྫ
    • Ϣʔβʔʮ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 Slide

  70. 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 Slide

  71. 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 Slide

  72. ܧ ঝ ͢ Δ 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 Slide

  73. ୈ ࡾ Ҿ ਺ ʹ ্ ॻ ͖ ͢ Δ ಺ ༰ Λ ͔ ͚ ͹ 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 Slide

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

    View Slide

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

    View Slide

  76. ͨ ͱ ͑ ͹ ε ϩ ο τ ̎ ͭ Λ ॲ ཧ ͢ Δ ϋ ϯ υ ϥ ʔ
    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 Slide

  77. 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 Slide

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

    View Slide

  79. 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 Slide

  80. 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 Slide

  81. 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 Slide

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

    View Slide

  83. 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 Slide

  84. 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 Slide

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

    View Slide

  86. 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 Slide

  87. 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 Slide

  88. ͦ Ε ͧ ΕͰ ͷ ఆ ٛ ͷ ॻ ͖ ํ
    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 Slide

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

    View Slide

  90. 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 Slide

  91. 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 Slide

  92. 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 Slide

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

    View Slide

  94. 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 Slide

  95. 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 Slide

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

    View Slide

  97. ͦ Ε ͧ ΕͰ ͷ σ ϓ ϩ Π ί Ϛ ϯ υ
    $ 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 Slide

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

    View Slide

  99. 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 Slide

  100. 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 Slide

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

    View Slide

  102. ཧ ղ ͢ Δ ͜ ͱ Ͱ
    ΄ ͔ ΁ ͷ Ԡ ༻ ΋ Մ ೳ ʹ
    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 Slide

  103. ந ৅ Խ ͠ ͨ ί ʔ υ Λ
    ެ ։ ͠ Α ͏
    • 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 Slide

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

    View Slide

  105. 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 Slide