Building an Alexa Skill without using the SDK
Amazon has written a "Hello World" example for building an Alexa Skill. At first glance, it looks like just what you need to get into Alexa Skills development because it's short and clear. But take a second look and you'll notice it requires an external dependency.
It brings in the alexa-sdk npm package. I'll show that not only don't you need the alexa-sdk to teach Alexa a Skill but you might actually be better off without it.
var Alexa = require('alexa-sdk');
The alexa-sdk claims to let you "focus on your skill's logic instead of boilerplate code." But it's exactly this boilerplate code that you want to see in a "Hello World" example. Without it, it's not a "Hello World" example for building an Alexa Skill. It's a "Hello World" example for building a Skill using the alexa-sdk, instead.
To put things right, here's the "Hello World" example that I think should be part of the Alexa Skills Kit. It's less than 15 lines long and has no external dependencies. Like any "Hello World" example worth its salt, it cuts things down to the bare necessities. It checks the Skill is being launched, using the incoming request type, and then responds with the "Hello World!" confirmation message.
exports.handler = function(event, context) {
if (event.request.type === 'LaunchRequest') {
context.succeed({
version: '1.0',
response: {
outputSpeech: {
type: 'SSML',
ssml: '<speak> Hello World! </speak>'
}
},
shouldEndSession: true
});
}
};
Put your skills to the test
Unit tests are the safety net for your refactoring high wire act. If you break something while changing code then your unit tests are there to catch you. But looking at Amazon's "Hello World" example again, it isn't obvious how you'd go about unit testing it. You can't see the handler's inputs and outputs because the boilerplate code is hidden away in the alexa-sdk.
My "Hello World" example is better suited for unit testing because it hides nothing away. You can see that the handler decides what to do based on the request type passed in. So, to set up a mocha unit test, I'd create a request object and populate its type with "LaunchRequest."
describe('Launch skill', function() {
it('should say "Hello World!"', function(done) {
var event = {
session: {
application: { applicationId: '' },
user: { userId: '' }
},
request: { type: 'LaunchRequest' }
};
var context = {};
helloWorld.handler(event, context);
})
});
You can also see that the handler responds using the succeed function on the context. So, to check that the response contains "Hello World!" I'd create a context object and put the asserts inside the callback.
var context = {
succeed: function(output) {
var speech = output.response.outputSpeech;
assert.equal(speech.type, 'SSML');
assert.ok(/Hello World!/.test(speech.ssml));
done();
},
fail: done
};
Alexa SDK?... Hmm, IDK
Once you've written unit tests that cover all the paths through your Skill, it's time to refactor. If a test fails then you know the refactor is unsuccessful, otherwise you know your Skill still works. It's at this stage that you might want to bring in the alexa-sdk. For example, I could run my test against Amazon's "Hello World" implementation and it would still pass.
But should you bring in the alexa-sdk at all? The alexa-sdk is object-oriented when the JavaScript community is embracing the functional. Instead of OO concepts like 'this' and mutable state, functional programming favors pure functions. You could refactor your Skill with Redux rather than the alexa-sdk. Here's what the "Hello World" example looks like with a reducer that returns the response message.
function launcher(state, action) {
switch (action.type) {
case 'LaunchRequest':
return '<speak> Hello World! </speak>';
default:
return state;
}
}
It's the handler's job to create the Redux store, dispatch the "LaunchRequest" action and respond with the message from the store. I know that the Skill still works after this refactor because my unit test passes.
exports.handler = function(event, context) {
var store = redux.createStore(launcher);
store.dispatch({ type: event.request.type });
context.succeed({
version: '1.0',
response: {
outputSpeech: {
type: 'SSML',
ssml: store.getState()
}
},
shouldEndSession: true
});
};
This Redux implementation scales well as the Skill grows in complexity. You can add Slot values and Session attributes to the initial state when creating the store. You can turn Intents into reducers and dispatch actions using the Intent name in the request. There you have it. A well-structured Alexa Skill and not an alexa-sdk in sight. Amazon's "Hello World" example should be dependency-free to make it clear that it's up to you how you build the Skill.
Graham Mendick, software engineer, Red Badger.
Published under license from ITProPortal.com, a Future plc Publication. All rights reserved.