FaaS

THE

S

erverless

AND THE

Simon MacDonald
@macdonst

How I saved myself some 💰 by converting  from

to

What do the Fast & the Furious movies have to do with Serverless?

Planning

Punching

Mechanic

Comic Relief

Characters

Functions

Fetch

Process

Transform

UI

Characters

  • Have their own agency
  • Internally consistent
  • Do one thing well

Functions

  • Stateless
  • Pure
  • One task per function

How does this relate to FaaS?

The Paul Walker Pattern(s)

Chaining

Fan in/out

Accomplishments

  • Brought down drug lords (multiple times)
  • Apprehend rouge secret agents (twice)
  • Prevent nuclear war
function queryLegacyAPI(query) {
    return fetch(api.getData(query))
        .then(response => response.text())
        .then(xmlString => parseXML(xmlString))
        .then(xml => { return xml; })
    );
}
function formatResponse(data) {
    const template = await loadTemplate();
    const html = json2html.transform(data, t);
    return html;
}
function transform2Json(data) {
    const xslt = await loadXslt();
    const parser = new DOMParser();
    const doc = parser.parseFromString(data, "application/xml");
    const json = doc.transformNode(xslt)
    return json;
}

Composing Functions

function queryLegacyAndFormat() {
    return compose(
            formatResponse,
            transform2Json,
            queryLegacyAPI
    )(query);
}

Plot Points

  • Car Thefts
  • Blackmail
  • Jailbreaks
  • Kidnappings
  • Murders!

Events

  • Git commit
  • Database action
  • Cron job
  • Blob storage
  • Analytics trigger
  • etc, etc, etc

What about Serverless?

On Premises

IaaS

PaaS

SaaS

Faas

It's about the Cars…

…and the Roads.

Furious…

  • Characters
  • Cars
  • Roads
     
  • Infinite possiblities

Serverless

  • Code (Faas)
  • Containers (SaaS/PaaS)
  • Other folks Infrastructure (IaaS)
  • Unbounded Scaling

Why?

🤔

Translation

Query

Static Assets

const Hapi = require('hapi');
const Joi = require('joi');
const fetch = require('node-fetch');
const credentials = require('./credentials');
const server = Hapi.server({ host: '0.0.0.0', port: 8000 });
async function start() {
  try {
    await server.register(require('inert'));
    server.route({
      method: 'POST',
      path: '/wolfram',
      config: {…}
    });
    server.route({
      method: 'POST',
      path: '/translate',
      config: {…}
    });
    server.route({
      method: 'GET',
      path: '/{param*}',
      handler: {
        directory: {
          path: 'public'
        }
      }
    });
    await server.start();
  } catch (err) {
    console.log(err);
    process.exit(1);
  }

  console.log('Server running at:', server.info.uri);
}
start();

Translation

Query

Static Assets

const Hapi = require('hapi');
const Joi = require('joi');
const fetch = require('node-fetch');
const credentials = require('./credentials');
const server = Hapi.server({ host: '0.0.0.0', port: 8000 });
async function start() {
  try {
    await server.register(require('inert'));
    server.route({
      method: 'POST',
      path: '/wolfram',
      config: {…}
    });
    server.route({
      method: 'POST',
      path: '/translate',
      config: {…}
    });
    server.route({
      method: 'GET',
      path: '/{param*}',
      handler: {
        directory: {
          path: 'public'
        }
      }
    });
    await server.start();
  } catch (err) {
    console.log(err);
    process.exit(1);
  }

  console.log('Server running at:', server.info.uri);
}
start();
const Hapi = require('hapi');
const Joi = require('joi');
const fetch = require('node-fetch');
const credentials = require('./credentials');

const server = Hapi.server({ host: '0.0.0.0', port: 8000 });

async function start() {
  try {
    await server.register(require('inert'));
    
    server.route({
      method: 'POST',
      path: '/wolfram',
      config: {…}
    });
    
    server.route({
      method: 'POST',
      path: '/translate',
      config: {…}
    });

    await server.start();
  } catch (err) {
    console.log(err);
    process.exit(1);
  }

  console.log('Server running at:', server.info.uri);
}
start();

Translation

Query

Static Assets

FaaS

server.route({
  method: 'POST',
  path: '/wolfram',
  config: {
    handler: function(req) {
      console.log('asking wolfram alpha');
      let url = `http://api.wolframalpha.com/v2/query?input=${
      req.payload.searchTerm
      }&appid=${credentials.wolfram}`;
      return fetch(url)
        .then(res => res.text())
        .then(text => { 
        	return text;
      });
    }
  }
});

server.route({
  method: 'POST',
  path: '/translate',
  config: {
    handler: function(req) {
      console.log('POST translate');
      let url = `https://www.googleapis.com/language/translate/v2?key=${
      credentials.google
      }&source=en&target=fr&q=${req.payload.searchTerm}`;
      return fetch(url)
        .then(res => res.json())
        .then(json => { 
        	return json;
      });
    }
  }
});
const fetch = require('node-fetch');
const credentials = require('./credentials');

module.exports = async function(context, req) {
  context.log('Wolfram HTTP trigger function processed a request.');

  if (req.query.searchTerm || (req.body && req.body.searchTerm)) {
    const searchTerm = req.query.searchTerm || req.body.searchTerm;
    context.log('search term = ' + searchTerm);
    let url = `http://api.wolframalpha.com/v2/query?input=${searchTerm}&appid=${
      credentials.wolfram
    }`;
    await fetch(url)
      .then(res => res.text())
      .then(text => {
        context.res = {
          status: 200,
          headers: { 'Content-Type': 'text/plain' },
          body: text
        };
        context.done();
      });
  } else {
    context.res = {
      status: 400,
      body: 'Please pass a name on the query string or in the request body'
    };
  }

  context.done();
};

Wolfram Function

const fetch = require('node-fetch');
const credentials = require('./credentials');

module.exports = async function(context, req) {
  context.log('Translate HTTP trigger function processed a request.');

  if (req.query.searchTerm || (req.body && req.body.searchTerm)) {
    const searchTerm = req.query.searchTerm || req.body.searchTerm;
    let url = `https://www.googleapis.com/language/translate/v2?key=${
      credentials.google
    }&source=en&target=fr&q=${searchTerm}`;
    context.log('search term = ' + searchTerm);
    await fetch(url)
      .then(res => res.json())
      .then(json => {
        context.res = {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
          body: json
        };
        context.done();
      });
  } else {
    context.res = {
      status: 400,
      body: 'Please pass a name on the query string or in the request body'
    };
  }

  context.done();
};

Translate Function

const Hapi = require('hapi');
const Joi = require('joi');
const fetch = require('node-fetch');
const credentials = require('./credentials');

const server = Hapi.server({ host: '0.0.0.0', port: 8000 });

async function start() {
  try {
    await server.register(require('inert'));
    
    server.route({
      method: 'POST',
      path: '/wolfram',
      config: {…}
    });
    
    server.route({
      method: 'POST',
      path: '/translate',
      config: {…}
    });

    await server.start();
  } catch (err) {
    console.log(err);
    process.exit(1);
  }

  console.log('Server running at:', server.info.uri);
}
start();

Hapi Server

const Hapi = require('hapi');
const Joi = require('joi');
const fetch = require('node-fetch');
const credentials = require('./credentials');

const server = Hapi.server({ host: '0.0.0.0', port: 8000 });

async function start() {
  try {
    await server.register(require('inert'));
    await server.start();
  } catch (err) {
    console.log(err);
    process.exit(1);
  }

  console.log('Server running at:', server.info.uri);
}
start();

🔥

Translation

Query

Static Assets

FaaS

💰

No more hosting fees!

💸

Until you add

CosmosDB

Thanks Linda Nichols (@lynnaloo)!

Check out her great talk on Serverless called

Not so FaaS…What's under the hood?

Summary

  • Functions should only do one thing
  • Everything a function needs should be passed in as input
  • The same input should produce the same output every time
  • Compose multiple small functions to make more complicated actions
  • Use events to fire off sequences of functions
  • Take advantage of other people's infrastructure so you can focus on the code