🔀 @brianleroux
💌 [email protected]
#!💻 personal computing
#!🕸 the interwebs
#!📱 mobile
#!💬 messaging? (at least we think so!)
📟 📠 📺 📻
- chatbots
- agents
- assistants
- conversational ui/ux
- conversation as a platform
😍 Everyone is talking about the same thing:
#!🤖 bots
But this manifestation is. These Bots are not here just to chat.
🐧 "This Bot is a superagent acting on your behalf
and it is better for some things than using an App"
🐮 "Similar to web tech a Bot can exist simultaneously
across different platforms and adapt seamlessly through
contexts they are in. The interfaces is conversation
and the conversation can move between platforms
transparently."
🐧 "Right!"
🐮 "👊🏾"
- irc
- email
- sms
- Slack
- Hipchat
- Messenger
- Telegram
- Kik
- Whatsapp
Kinda rad.
First 1 million requests per month are free. $0.20 per 1 million requests thereafter ($0.0000002 per request)
pricing remains unannounced
Function requests are charged per million requests, with the first 1 million requests free. [Then ambigous] pay for what you use with compute metered to the nearest 100ms at Per/GB
I find Azure pricing nebulous and confusing. Maybe thats just me.
However, I believe any of these solutions is a fine choice.
- The lockin risk at the function level is trivial to avoid
- Vendors increasing exponentially while compute continues to get cheaper
- Accelerant (time to deploy and ease of maintenance)
- AWS services surrounding Lambda are amazing and by far industry leading
👉 A new way of thinking for your dev team 👉 Also the tools for container techniques are still new(ish) and often raw 👉 Many repos that produce even smaller deployment artifacts makes dep mgmt tricky 👉 Many repos can also make deployment tricky (to mitigate automate everything from day zero)
(Truly it is cheap enough to experiment with all them and I totally recommend you do.)
---
ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
Let's dig into Lambdas
λλλλλλλλλλλλλλλλλλλλλλ
λλλλλλλλλλλλλλλλλλλλλλ
λλλλλλλλλλλλλλλλλλλλλλ
λλλλλλλλλλλλλλλλλλλλλλ
λλλλλλλλλλλλλλλλλλλλλλ
λλλλλλλλλλλλλλλλλλλλλλ
λλλλλλλλλλλλλλλλλλλλλλ
λλλλλλλλλλλλλλλλλλλλλλ
λλλλλλλλλλλλλλλλλλλλλλ
λλλλλλλλλλλλλλλλλλλλλλ
λλλλλλλλλλλλλλλλλλλλλλ
λλλλλλλλλλλλλλλλλλλλλλ
---
## 📚 There are three types of Lambda function
🗣 .-------------------------------------------------------------------------------------. | 1. 📗 Lambdas that return a value (Typically, but not exclusively, thru API Gateway) | | 2. 📘 Lambdas as an event source (S3 events, SNS topics and DynamoDB triggers) | | 3. 📙 Lambdas that run on a schedule | '-------------------------------------------------------------------------------------' .--------------------===========--------------------------------. ( AWS doesn't make a distinction with 2 & 3 ) ( 'CloudWatch Events - Schedule' is an event source ) ( However it is a good idea to seperate these types of function ) '-----------------=========-------------------------------------' 🗿
```javascript
/**
* a typical Lambda signature
*/
exports.handler = function(event, context) {
// event is an arbitrary payload of data from whatever source invoked the Lambda
// context is an object with information about the execution environment
// AND it has function members for asynchronous: `succeed`, `fail` or shorthand err first `done`
// alternately you can pass third param of a node style callback (aka an errback)
context.done(null, {ok:true})
}
/**
*/
<blockquote> </blockquote>
<blockquote> </blockquote>
<blockquote> </blockquote>
---
## the pick axes ⛏ the shovels 🕳 the building blocks 🔩
| Lets write some code | |
|:------------------------------------------------ | --------------------- |
| 💎 Hello world Lambda function |💎|
| 💎 Expose endopint to the web with API Gateway |💎💎|
| 💎 Wire up SMS with Twilio |💎💎💎|
| 💎 Port to Slack |💎💎💎💎|
---
### :satellite::satellite: a lambda that returns a value :mailbox:
Here is a vanilla AWS Lambda example for performing a sum. Given `event.query.x = 1` it will return `{count:2}`.
```javascript
exports.handler = function sum(event, context) {
var errors = []
if (typeof event.query === 'undefined') {
errors.push(ReferenceError('missing event.query'))
}
if (event.query && typeof event.query != 'object') {
errors.push(TypeError('event.query not an object'))
}
if (typeof event.query.x === 'undefined') {
errors.push(ReferenceError('event.query not an object'))
}
if (event.query.x && typeof event.query.x != 'number') {
errors.push(TypeError('event.query not an object'))
}
if (errors.length) {
// otherwise Error would return [{}, {}, {}, {}]
var err = errors.map(function(e) {return e.message})
context.fail(err)
}
else {
context.succeed({count:event.query.x + 1})
}
}
Take this simple url for example:
https://fake.com/posts/3/comments/5?success=true
event.params.post_id
event.params.comment_id
event.query.success
¯\_(ツ)_/¯
---
## Postels Law: maybe not such a good idea 🤔
- It isn't heresy to acknowledge `HTTP` is a weird protocol
- In the example above we are validating one querystring parameter `x`... just imagine a big payload! 😮
- Worse still, `Error` requires manual serialization: remember [a string is not an error](http://www.devthought.com/2011/12/22/a-string-is-not-an-error)
- The latter part of this vanilla code uses the funky AWS context object
We can do better!
---
## 😻🎀 microlibraries for microservices
```javascript
var validate = require('@smallwins/validate')
var lambda = require('@smallwins/lambda')
function sum(event, callback) {
var schema = {
'query': {required:true, type:Object},
'query.x': {required:true, type:Number}
}
var errors = validate(event, schema)
if (errors) {
callback(errors)
}
else {
var result = {count:event.query.x + 1}
callback(null, result)
}
}
exports.handler = lambda(sum)
var validate = require('@smallwins/validate')
var lambda = require('@smallwins/lambda')
function valid(event, callback) {
var schema = {
'body': {required:true, type:Object},
'body.username': {required:true, type:String},
'body.password': {required:true, type:String}
}
validate(event, schema, callback)
}
function authorized(event, callback) {
var loggedIn = event.body.username === 'sutro' && event.body.password === 'cat'
if (!loggedIn) {
// err first
callback(Error('not found'))
}
else {
// successful login
event.account = {
loggedIn: loggedIn,
name: 'sutro furry pants'
}
callback(null, event)
}
}
function safe(event, callback) {
callback(null, {account:event.account})
}
exports.handler = lambda(valid, authorized, safe)
AWS DynamoDB triggers invoke a Lambda function if anything happens to a table. The payload is usually a big array of records. @smallwins/lambda
allows you to focus on processing a single record but executes the function in parallel on all the results in the Dynamo invocation. For convenience the same middleware chaining is supported.
var lambda = require('@smallwins/lambda')
function save(record, callback) {
console.log('save a version ', record)
callback(null, record)
}
exports.handler = lambda.sources.dynamo.save(save)
lambda(...fns)
create a Lambda that returns a serialized json result {ok:true|false}
lambda([fns], callback)
create a Lambda and handle result with your own errback formatterlambda.local(fn, fakeEvent, (err, result)=>)
run a Lambda locally offline by faking the event objlambda.sources.dynamo.all(...fns)
run on INSERT, MODIFY and REMOVElambda.sources.dynamo.save(...fns)
run on INSERT and MODIFYlambda.sources.dynamo.insert(...fns)
run on INSERT onlylambda.sources.dynamo.modify(...fns)
run on MODIFY onlylambda.sources.dynamo.remove(...fns)
run on REMOVE only
@smallwins/lambda
includes some helpful automation code perfect for npm scripts. If you have a project that looks like this:
project-of-lambdas/
|-test/
|-src/
| '-lambdas/
| |-signup/
| | |-index.js
| | |-test.js
| | '-package.json <--- name property should equal the deployed lambda name
| |-login/
| '-logout/
'-package.json
And a package.json
like this:
{
"name":"project-of-lambdas",
"scripts": {
"create":"AWS_PROFILE=smallwins lambda-create",
"list":"AWS_PROFILE=smallwins lambda-list",
"deploy":"AWS_PROFILE=smallwins lambda-deploy",
"invoke":"AWS_PROFILE=smallwins lambda-invoke",
"local":"AWS_PROFILE=smallwins lambda-local",
"deps":"AWS_PROFILE=smallwins lambda-deps",
"log":"AWS_PROFILE=smallwins lambda-log"
}
}
You get:
This is 🔑! Staying in the flow with your terminal by reducing hunts for information in the AWS Console. :shipit:📈
forgot
at src/lambdas/forgot
brian
console.log
statements show up here)Note: these scripts assume each lambda has it's own nested package.json
file with a name
property that matches the lambda name.
@smallwins/lambda
is deliberately a data flow control library with some convienance scripts@smallwins/lambda
is not a confiuration utility (aka a framework)mkdir lambdabot
cd lambdabot/ && npm init -y
npm i @smallwins/lambda --save
Then add the following to your package.json
:
{
"name": "bots",
"version": "1.0.0",
"scripts": {
"create": "lambda-create",
"list": "lambda-list",
"deploy": "lambda-deploy",
"invoke": "lambda-invoke",
"invoke": "lambda-local",
"deps": "lambda-deps",
"log": "lambda-log"
},
"dependencies": {
"@smallwins/lambda": "^4.8.0"
}
}
npm run create lambdabot
npm i
npm run deploy lambdabot brian
npm run invoke lambdabot brian ""
lets look at the code
All good Bots will always respond to:
And make sure your Bot can bashfully acknowledge things it does not understand!
🤖
|
|
|
|
'--------------------------------------------------------.
| thx! }
| ping me anytime }
| [email protected] }
'-----------------'
I plan to update this repo with: