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

[German] Alexa, sag Hallo zu Chefkoch

[German] Alexa, sag Hallo zu Chefkoch

Von Null auf Hundert mit Golang, Apex und AWS Lambda.

Golexa - A little Go library to easily handle Alexa custom skill requests
https://github.com/b00giZm/golexa

Follow me:
https://twitter.com/b00gizm
https://github.com/b00gizm

Avatar for Pascal Cremer

Pascal Cremer

November 17, 2016
Tweet

More Decks by Pascal Cremer

Other Decks in Programming

Transcript

  1. Golang: Eine statisch kompilierte, "C"- ähnliche Programmiersprache mit einem sehr

    kleinen Sprachumfang, hohen Performance Benefits und einem einfachen Concurrency Modell.
  2. func Greeting(name string) string {
 return "Hello " + name


    } function greeting($name) {
 return 'Hello '.$name;
 } GOLANG PHP
  3. type Person struct{
 FirstName, LastName string
 }
 
 func (p

    *Person) FullName() string {
 return p.FirstName + " " + p.LastName
 } fuckFace := &Person{"Donald", "Trump"} class Person
 {
 private $firstName;
 private $lastName;
 
 public function __construct($firstName, $lastName)
 {
 $this->firstName = $firstName;
 $this->lastName = $lastName;
 }
 
 public function getFullName()
 {
 return $this->firstName." ".$this->lastName;
 }
 }
 
 $fuckFace = new Person('Donald', 'Trump'); GOLANG PHP
  4. func ScoreForPlayer(name string) (int, bool) {
 ...
 } playerName :=

    "Alice"
 score, ok := ScoreForPlayer(playerName)
 if (!ok) {
 panic("No score for player " + playerName)
 }
 
 fmt.Println(playerName + "'s score is " + strconv.Itoa(score)) function getScoreForPlayer($playerName) {
 ...
 }
 
 $playerName = "Alice";
 $score = getScoreForPlayer($playerName);
 if (!$score) {
 die("No score for player ".$playerName);
 }
 
 echo $playerName."'s score is ".$score."\n"; GOLANG PHP
  5. type Person struct{ FirstName string `json:"first_name"` LastName string `json:"last_name"` }

    func (p *Person) FullName() string {
 return p.FirstName + " " + p.LastName
 } jsonString := []byte{`{ "first_name": "Donald", "last_name": "Trump" }`} var fuckFace Person _ := json.Unmarshal(jsonString, &fuckFace) fmt.Println(fuckFace.FullName()) // "Donald Trump" GO JSON AWESOMENESS
  6. type Person struct{ FirstName string `json:"first_name"` LastName string `json:"last_name"` }

    func (p *Person) FullName() string {
 return p.FirstName + " " + p.LastName
 } jsonString := []byte{`{ "first_name": "Donald", "last_name": "Trump" }`} var fuckFace Person _ := json.Unmarshal(jsonString, &fuckFace) fmt.Println(fuckFace.FullName()) // "Donald Trump" GO JSON AWESOMENESS
  7. type Person struct{ FirstName string `json:"first_name"` LastName string `json:"last_name"` }

    func (p *Person) FullName() string {
 return p.FirstName + " " + p.LastName
 } jsonString := []byte{`{ "first_name": "Donald", "last_name": "Trump" }`} var fuckFace Person _ := json.Unmarshal(jsonString, &fuckFace) fmt.Println(fuckFace.FullName()) // "Donald Trump" GO JSON AWESOMENESS
  8. type Person struct{ FirstName string `json:"first_name"` LastName string `json:"last_name"` }

    func (p *Person) FullName() string {
 return p.FirstName + " " + p.LastName
 } jsonString := []byte{`{ "first_name": "Donald", "last_name": "Trump" }`} var fuckFace Person _ := json.Unmarshal(jsonString, &fuckFace) fmt.Println(fuckFace.FullName()) // "Donald Trump" GO JSON AWESOMENESS
  9. func FetchTopRecipes(categoryID int, c chan RecipeList) { result := ...

    c <- result
 } c := make(chan RecipeList)
 
 go FetchTopRecipes(123, c)
 go FetchTopRecipes(321, c)
 results := make([]RecipeList, 2) append(results, <-c) append(results, <-c) GO CONCURRENCY AWESOMENESS
  10. func FetchTopRecipes(categoryID int, c chan RecipeList) {
 result := ...

    c <- result
 } c := make(chan RecipeList)
 
 go FetchTopRecipes(123, c)
 go FetchTopRecipes(321, c)
 results := make([]RecipeList, 2) append(results, <-c) append(results, <-c) GO CONCURRENCY AWESOMENESS
  11. Alexa, frage Chefkoch nach Pasta Rezepten. Chefkoch Skill Utterances RecipeOfCategory

    {Category} Rezepte RecipeOfCategory zeig mir {Category} Rezepte RecipeOfCategory ich hätte gerne {Category} Rezepte RecipeOfCategory gib mir Rezepte aus der Kategorie {Category} ... { ... "request": { ... "type": "IntentRequest", "locale": "de_DE", ... "intent": { "name": "RecipeOfCategory", "slots": { "string": { "name": "Category", "value": "Pasta" } } } } } { ... "response": { "outputSpeech": { "type": "PlainText", "text": "Ich habe dir...", }, "shouldEndSession": false } } Ich habe dir fünf der besten Pasta-Rezepte rausgesucht.... ❤ ❤
  12. Apex: Einfaches Tool zum Deployment und Management von AWS Lambda

    Funktionen. Es bietet außerdem die Möglichkeit über einen Node.js Shim weitere Programmiersprachen als die (Lambda-)Nativen zu verwenden.
  13. 0101001010 1010110101 0100101001 1010101... 0101001010 1010110101 0100101001 1010101... child_process STDIN

    STDOUT go build... { "request": { "type": "IntentRequest", "locale": "de_DE", "intent": { ... } } } apex deploy
  14. var child = require('child_process');
 var byline = require('./byline');
 
 var

    proc = child.spawn('./main', { stdio: ['pipe', 'pipe', process.stderr] });
 
 var out = byline(proc.stdout);
 
 out.on('data', function(line) {
 if (process.env.DEBUG_SHIM) console.log('[shim] parsing: %j', line);
 var msg = JSON.parse(line);
 ctx.done(msg.error, msg.value)
 });
 
 exports.handle = function(event, context) {
 ctx = context;
 
 proc.stdin.write(JSON.stringify({
 "event": event,
 "context": context
 })+'\n');
 }; NODE.JS SHIM
  15. var child = require('child_process');
 var byline = require('./byline');
 
 var

    proc = child.spawn('./main', { stdio: ['pipe', 'pipe', process.stderr] });
 
 var out = byline(proc.stdout);
 
 out.on('data', function(line) {
 if (process.env.DEBUG_SHIM) console.log('[shim] parsing: %j', line);
 var msg = JSON.parse(line);
 ctx.done(msg.error, msg.value)
 });
 
 exports.handle = function(event, context) {
 ctx = context;
 
 proc.stdin.write(JSON.stringify({
 "event": event,
 "context": context
 })+'\n');
 }; NODE.JS SHIM
  16. var child = require('child_process');
 var byline = require('./byline');
 
 var

    proc = child.spawn('./main', { stdio: ['pipe', 'pipe', process.stderr] });
 
 var out = byline(proc.stdout);
 
 out.on('data', function(line) {
 if (process.env.DEBUG_SHIM) console.log('[shim] parsing: %j', line);
 var msg = JSON.parse(line);
 ctx.done(msg.error, msg.value)
 });
 
 exports.handle = function(event, context) {
 ctx = context;
 
 proc.stdin.write(JSON.stringify({
 "event": event,
 "context": context
 })+'\n');
 }; NODE.JS SHIM
  17. var child = require('child_process');
 var byline = require('./byline');
 
 var

    proc = child.spawn('./main', { stdio: ['pipe', 'pipe', process.stderr] });
 
 var out = byline(proc.stdout);
 
 out.on('data', function(line) {
 if (process.env.DEBUG_SHIM) console.log('[shim] parsing: %j', line);
 var msg = JSON.parse(line);
 ctx.done(msg.error, msg.value)
 });
 
 exports.handle = function(event, context) {
 ctx = context;
 
 proc.stdin.write(JSON.stringify({
 "event": event,
 "context": context
 })+'\n');
 }; NODE.JS SHIM
  18. var child = require('child_process');
 var byline = require('./byline');
 
 var

    proc = child.spawn('./main', { stdio: ['pipe', 'pipe', process.stderr] });
 
 var out = byline(proc.stdout);
 
 out.on('data', function(line) {
 if (process.env.DEBUG_SHIM) console.log('[shim] parsing: %j', line);
 var msg = JSON.parse(line);
 ctx.done(msg.error, msg.value)
 });
 
 exports.handle = function(event, context) {
 ctx = context;
 
 proc.stdin.write(JSON.stringify({
 "event": event,
 "context": context
 })+'\n');
 }; NODE.JS SHIM
  19. package main
 
 import ( 
 "encoding/json"
 "github.com/apex/go-apex" 
 )


    
 func main() {
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { 
 
 }) 
 }
  20. package main
 
 import ( 
 "encoding/json"
 "github.com/apex/go-apex" 
 )


    
 func main() {
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { 
 
 }) 
 }
  21. package main
 
 import ( 
 "encoding/json"
 "github.com/apex/go-apex" 
 )


    
 func main() {
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { 
 
 }) 
 }
  22. package main
 
 import ( 
 "encoding/json"
 "github.com/apex/go-apex" g "github.com/b00giZm/golexa"


    )
 
 func main() {
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { 
 app := g.Default() return app.Process(event)
 })
 }
  23. package main
 
 import ( 
 "encoding/json"
 "github.com/apex/go-apex" g "github.com/b00giZm/golexa"


    )
 
 func main() {
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { 
 app := g.Default() app.OnIntent(func(a *g.Alexa, intent *g.Intent, req *g.Request, session *g.Session) *g.Response) { }) return app.Process(event)
 })
 }
  24. package main
 
 import ( 
 "encoding/json"
 "github.com/apex/go-apex" g "github.com/b00giZm/golexa"


    )
 
 func main() {
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { 
 app := g.Default() app.OnIntent(func(a *g.Alexa, intent *g.Intent, req *g.Request, session *g.Session) *g.Response) { if intent.Name != "GetRecipeOfTheDay" { return nil } }) return app.Process(event)
 })
 }
  25. package main
 
 import ( "chefkoch.de/chefkoch-skill/lib/api" 
 "encoding/json"
 "github.com/apex/go-apex" g

    "github.com/b00giZm/golexa"
 )
 
 func main() {
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { apiClient := api.DefaultClient() 
 app := g.Default() app.OnIntent(func(a *g.Alexa, intent *g.Intent, req *g.Request, session *g.Session) *g.Response) { if intent.Name != "GetRecipeOfTheDay" { return nil } recipe, _ := apiClient.FetchRecipeOfTheDay() }) return app.Process(event)
 })
 }
  26. package main
 
 import ( "chefkoch.de/chefkoch-skill/lib/api" 
 "encoding/json"
 "github.com/apex/go-apex" g

    "github.com/b00giZm/golexa"
 )
 
 func main() {
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { apiClient := api.DefaultClient() 
 app := g.Default() app.OnIntent(func(a *g.Alexa, intent *g.Intent, req *g.Request, session *g.Session) *g.Response) { if intent.Name != "GetRecipeOfTheDay" { return nil } recipe, err := apiClient.FetchRecipeOfTheDay() if err != nil { // Handle error } }) return app.Process(event)
 })
 }
  27. package main
 
 import ( "chefkoch.de/chefkoch-skill/lib/api" 
 "encoding/json"
 "github.com/apex/go-apex" g

    "github.com/b00giZm/golexa"
 )
 
 func main() {
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { apiClient := api.DefaultClient() 
 app := g.Default() app.OnIntent(func(a *g.Alexa, intent *g.Intent, req *g.Request, session *g.Session) *g.Response) { if intent.Name != "GetRecipeOfTheDay" { return nil } recipe, _ := apiClient.FetchRecipeOfTheDay() }) return app.Process(event)
 })
 }
  28. package main
 
 import ( "chefkoch.de/chefkoch-skill/lib/api" "chefkoch.de/chefkoch-skill/lib/helpers"
 "encoding/json"
 "github.com/apex/go-apex" g

    "github.com/b00giZm/golexa"
 )
 
 func main() {
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { apiClient := api.DefaultClient() templateParser := helpers.DefaultTemplateParser() 
 app := g.Default() app.OnIntent(func(a *g.Alexa, intent *g.Intent, req *g.Request, session *g.Session) *g.Response) { if intent.Name != "GetRecipeOfTheDay" { return nil } recipe, _ := apiClient.FetchRecipeOfTheDay() str, _ := templateParser.Parse("GetRecipeOfTheDay", recipe) }) return app.Process(event)
 })
 }
  29. package main
 
 import ( "chefkoch.de/chefkoch-skill/lib/api" "chefkoch.de/chefkoch-skill/lib/helpers"
 "encoding/json"
 "github.com/apex/go-apex" g

    "github.com/b00giZm/golexa"
 )
 
 func main() {
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { apiClient := api.DefaultClient() templateParser := helpers.DefaultTemplateParser() 
 app := g.Default() app.OnIntent(func(a *g.Alexa, intent *g.Intent, req *g.Request, session *g.Session) *g.Response) { if intent.Name != "GetRecipeOfTheDay" { return nil } recipe, _ := apiClient.FetchRecipeOfTheDay() str, _ := templateParser.Parse("GetRecipeOfTheDay", recipe) return a.Response() }) return app.Process(event)
 })
 }
  30. package main
 
 import ( "chefkoch.de/chefkoch-skill/lib/api" "chefkoch.de/chefkoch-skill/lib/helpers"
 "encoding/json"
 "github.com/apex/go-apex" g

    "github.com/b00giZm/golexa"
 )
 
 func main() {
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { apiClient := api.DefaultClient() templateParser := helpers.DefaultTemplateParser() 
 app := g.Default() app.OnIntent(func(a *g.Alexa, intent *g.Intent, req *g.Request, session *g.Session) *g.Response) { if intent.Name != "GetRecipeOfTheDay" { return nil } recipe, _ := apiClient.FetchRecipeOfTheDay() str, _ := templateParser.Parse("GetRecipeOfTheDay", recipe) return a.Response().AddPlainTextSpeech(str) }) return app.Process(event)
 })
 }
  31. package main
 
 import ( "chefkoch.de/chefkoch-skill/lib/api" "chefkoch.de/chefkoch-skill/lib/helpers"
 "encoding/json"
 "github.com/apex/go-apex" g

    "github.com/b00giZm/golexa"
 )
 
 func main() {
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { apiClient := api.DefaultClient() templateParser := helpers.DefaultTemplateParser() 
 app := g.Default() app.OnIntent(func(a *g.Alexa, intent *g.Intent, req *g.Request, session *g.Session) *g.Response) { if intent.Name != "GetRecipeOfTheDay" { return nil } recipe, _ := apiClient.FetchRecipeOfTheDay() str, _ := templateParser.Parse("GetRecipeOfTheDay", recipe) return a.Response().AddPlainTextSpeech(str).AddSessionAttributes(g.SessionAttributes{ "currentRecipe": recipe, }) }) return app.Process(event)
 })
 }
  32. package main
 
 import ( "chefkoch.de/chefkoch-skill/lib/api" "chefkoch.de/chefkoch-skill/lib/helpers"
 "encoding/json"
 "github.com/apex/go-apex" g

    "github.com/b00giZm/golexa"
 )
 
 func main() {
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { apiClient := api.DefaultClient() templateParser := helpers.DefaultTemplateParser() 
 app := g.Default() app.OnIntent(func(a *g.Alexa, intent *g.Intent, req *g.Request, session *g.Session) *g.Response) { if intent.Name != "GetRecipeOfTheDay" { return nil } recipe, _ := apiClient.FetchRecipeOfTheDay() str, _ := templateParser.Parse("GetRecipeOfTheDay", recipe) return a.Response().AddPlainTextSpeech(str).AddSessionAttributes(g.SessionAttributes{ "currentRecipe": recipe, }).KeepSessionAlive() }) return app.Process(event)
 })
 }
  33. type RecipeOfTheDayHandler struct { BaseHandler } func (h *RecipeOfTheDayHandler) HandleIntent(a

    *g.Alexa, intent *g.Intent, req *g.Request, session *g.Session) *g.Response { ... } ... alexaHandlers := &[]golexa.Handler{ NewWelcomeHandler(client, parser, ctx), NewLogHandler(), NewRecipeOfTheDayHandler(client, parser, ctx), NewRecipesOfCategoryHandler(client, parser, ctx, categories), NewIngredientsHandler(client, parser, ctx, categories), NewSendRecipeHandler(client, parser, ctx, categories), ... NewAmazonIntentsHandler(client, parser, ctx, categories), NewGoodByeHandler(client, parser, ctx), } app := g.Init(alexaHandlers) ... 
 apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) { return app.Process(event)
 })
  34. Entwicklung des eigentlichen Skills (+ Unit Tests) in weniger als

    einer Woche Sehr stabile Builds ("Nebeneffekt" einer kompilierten Sprache) Deployments mit Apex < 10 Sekunden Teils relativ viel Boilerplate Code (Limitierungen von Go, z.B. fehlende Generics) Debugging ist nicht sehr komfortabel (Logging nach STDERR)
  35. ◻ Umsetzung des Feedbacks der ersten Echo-Kunden (z.B. Zutatensuche) ◻

    Unterscheidung neuer User - Wiederkehrender User ◻ Account-Linking mit Chefkoch (Business Value!) ◻ Migration auf eigene Infrastruktur (?)