Giter Club home page Giter Club logo

fdk's Introduction

IMPORTANT: This library is deprecated. Please use https://github.com/serverless/event-gateway-sdk instead.

Function Development Kit (aka FDK)

Node.js library to improve developer experience developing Serverless applications.

It contains a client to configure and interact with the Event Gateway.

Build Status

Install (Node)

npm install @serverless/fdk

Install (Browser)

<script type="text/javascript" src="https://unpkg.com/@serverless/fdk@latest/dist/fdk.min.js"></script>

The FDK then will be attached to window e.g. and you can access it via window.fdk

Create an Event Gateway Client

const fdk = require('@serverless/fdk');
const eventGateway = fdk.eventGateway({
  url: 'http://localhost',
})

Optional Properties for eventGateway

{
  // defaults to the provide URL + default config port 4001
  configurationUrl: 'http://localhost:4001'
  // optional property to provide their own http lib
  fetchClient: fetch
}

Invoke a Function

eventGateway.invoke({
  functionId: "createUser",
  data: { name: "Max" },
})

Returns a Promise with the response.

The value of data is converted using JSON.stringify by default since the default dataType is application/json. This is not happening and the value is passed as it is when the property dataType is provided.

Invoke a Function with a Custom Data Type

eventGateway.invoke({
  functionId: "createUser",
  data: "Max",
  dataType: "text/plain",
})

Emit an Event

eventGateway.emit({
  event: "userCreated",
  data: { name: "Max" },
})

Returns a Promise and when resolved the response only indicates if the Event Gateway received the event. Responses from any subscribed functions are not part of the response.

The value of data is converted using JSON.stringify by default since the default dataType is application/json. This is not happening and the value is passed as it is when the property dataType is provided.

Emit an Event with a Custom Data Type

eventGateway.emit({
  event: "userCreated",
  data: "This is a string message.",
  dataType: "text/plain",
})

Configure an Event Gateway

Configure accepts an object of function and subscription definitions. The idea of exposing one configuration function is to provide developers with convenient utility to configure an Event Gateway in one call rather then dealing with a chain of Promise based calls. Nevertheless in addition we expose a wrapper function for each low-level API call to the Event Gateway described in this section.

eventGateway.configure({
  // list of all the functions that should be registered
  functions: [
    {
      functionId: "helloWorld"
      provider: {
        type: "awslambda"
        arn: "xxx",
        region: "us-west-2",
      }
    },
    {
      functionId: "sendWelcomeMail"
      provider: {
        type: "gcloudfunction"
        name: "sendWelcomeEmail",
        region: "us-west-2",
      }
    }
  ],
  // list of all the subscriptions that should be created
  subscriptions: [
    {
      event: "http",
      method: "GET",
      path: "/users",
      functionId: "helloWorld"
    },
    {
      event: "user.created",
      functionId: "sendEmail"
    }
  ]
})

Returns a promise which contains all the same list of functions and subscriptions in the same structure and order as provided in the configuration argument.

eventGateway.configure({ config })
  .then((response) => {
    console.log(response)
    // {
    //   functions: [
    //     { functionId: 'xxx', … },
    //     { functionId: 'xxx', … }
    //   ],
    //   subscriptions: [
    //     { subscriptionId: 'xxx', … },
    //     { subscriptionId: 'xxx', … }
    //   ]
    // }
  })

Reset the configuration

Reset removes all the existing subscriptions and functions.

eventGateway.resetConfiguration()

Further Event Gateway Functions

// Returns a function
eventGateway.registerFunction({
  functionId: "sendEmail"
  provider: {
    type: "awslambda"
    arn: "xxx",
    region: "us-west-2",
  }
})

// Returns undefined
eventGateway.deleteFunction({ functionId: "sendEmail" })

// Returns an Array of functions
eventGateway.listFunctions()

// Returns a subscription: { subscriptionId, event, functionId}
eventGateway.subscribe({
  event: "user.created",
  functionId: "sendEmail"
})

// Returns undefined
eventGateway.unsubscribe({
  subscriptionId: "user.created-sendEmail"
})

// Returns an Array of subscriptions
eventGateway.listSubscriptions()

Contribute

If you are interested to contribute we recommend to check out the Contributing document as it explains how to get started and some of the design decisions for this library.

fdk's People

Contributors

brianneisler avatar flomotlik avatar mthenw avatar nikgraf avatar p0wl avatar pmuens avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fdk's Issues

Generic function handler

Generic handler

handler(function(event, [context], callback){}) (or λ(...))

Arguments:

  • function(event, [context], callback) - hander function. Function accepts following arguments:
    • event - any type - event payload,
    • context (optional) - object - additional info
    • callback - function - callback function result callback(err, result).
const stdlib = require('@serverless/stdlib')();

exports.userFunc = stdlib.handler((event, callback) => {
  callback(new Error('not implemented'))  
});

Notes

  • Ad #14 (comment) @dougmoscrop for now we will start with callback. I would like to have promise based version but we will start minimal.

  • Ad #14 (comment) @ac360 context from global object is doable in AWS Lambda (because there is one execution per container). I'm not sure how this thing looks in GCF, Azure and IBM but it wouldn't assume that's true. So it's safer to have context object as a optional argument. Let me know what's your thoughts here.

If you are writing truly portable functions, then wouldn't an optional argument require writing logic at the beginning of every function to check if context was passed in?

Nope. Before calling user function we can check how many arguments it accepts and based on that we can pass 2 or 3 args.

exports.userFunc = stdlib.handler((event, callback) => {
  callback(new Error('not implemented'))  
});

exports.userFunc = stdlib.handler((event, context, callback) => {
  callback(new Error('not implemented'))  
});

Both will work. No need to do if.

emit - async call via event source

Serverless framework already support different event sources (S3, APIG). Instead of explicitly calling callAsync it would be more flexible to provide a way to emit events (from within function) that other function can treat as an event source.

Example

serverless.yml:

functions:
  sendWelcomeEmail:
    events:
      - function: userCreated

Function sendWelcomeEmail is async called every time userCreated event is emitted from functions in the same service.

userCreate.js:

sdk.emit('userCreated', '[email protected]');

Optionally more advanced event source object can be defined:

functions:
  sendWelcomeEmail:
    events:
      - function:
        event: userCreated
        function: create # listen to userCreated events only emitted by create function

Reduce size of the browser build

Possible enhancements

  • enable minification
  • direct imports of Ramda functions or replace Ramda
  • replace isomorphic fetch with github's fetch polyfill

Automatically authorize to EG

If a SERVERLESS_APPLICATION_TOKEN environment variable is present, then it should be submitted as a header of the form Authorization: bearer <token> on any requests to the event gateway.

isColdStart method

To provide users with an easy way to track if something is a cold start we should provide a stable SDK method they can use.

sdk.isColdStart()

will return true the first time and false for any other invocations in that same deployed lambda.

Universal Function Handler

Goals:

  • Create a single function handler for synchronous and asynchronous functions that works across all providers.
  • Offer a better user experience.

Improve error messages

Right now we pass the error messages to configure and console.log them. The result is that no info is printed. Instead we should get out the message from the errors.

Rename

It's not actually SDK.

Propositions:

  • stdlib - require('@serverless/stdlib')
  • lib - require('@serverless/lib')

cc @serverless/team

Unsubscribe for http event not possible if subscriptionId contains "special characters"

I want to unsubscribe an http event with the subscriptionId http-GET-%2Fhello.

Unfortunately I get the following error message back:

Error: Failed to unsubscribe the subscription http-GET-%2Fhello and couldn't parse error body.

Special characters like %2F are not problem for the Event Gateway (according to @mthenw). My assumption is that the fetch call translates %2F into / so that the requests here is translated to /v1/subscriptions/http-GET-/hello which erros out because the path is not valid on the event gateways side.

trigger method

Almost the same as sync call method:

sdk.trigger('sendWelcomeEmail', '[email protected]', callback)

trigger(name, args, callback)

Parameters:

  • name - string|array - function name(s)
  • args - any type - arguments to pass to called function
  • callback - function - takes err and result. callback parameter informs if trigger has been successfully submitted. It doesn't mean that function has been called.

Currently there is no way to provide configurable retry logic. It's worth to read how it's solved in AWS (http://docs.aws.amazon.com/lambda/latest/dg/retries-on-errors.html).

call method

Implement SDK.call method that takes the name of a function and invokes that function through the AWS SDK

Context object

Handler (#15) should also expose context object. context should provide a way to get runtime information (in some cases they can be request/event bounded).

API proposition:

  • isColdStart
  • provider
  • memoryLimit
  • getCurrentDuration()

I'm not sure if we should provide methods or fields. Things like memory or provider are pretty static. No need to provide getter for them.

Improve readme

  • Link to the event gateway
  • restructure to optimze for newcomers

Naming clarity

  1. The primary exports of the library are stdlib().handler() and stdlib.call(). In one of these, there is a function invocation stdlib(), and in the other a singleton object reference stdlib. This inconsistency does not clearly communicate anything semantic.

    It would be better for stdlib always to be used as a static/singleton container for stdlib methods, with the functionality now served by stdlib() as a method call being moved into a method within the stdlib namespace. @mthenw suggests stdlib.use()

  2. The term call is inconsistent with the CLI and other documented features of the framework which use the term invoke to describe the same operation. We ought to rename this method.

Isomorphism tests for node/browser equivalence

The FDK should work equivalently in back-end and front-end applications. The test suite currently runs within the node runtime, but should also cover headless Chrome usage to ensure isomorphic operation.

Using promises

Right now the plan is to follow callback pattern. We might want to provide a way to return promise from each method or even make them promise-based only.

cc @nikgraf @eahefnawy @pmuens

Feature Request: Shorthand signature for emit

The current spec outlines the emit function signature as the following

gateway.emit({
  event: "userCreated",
  data: JSON.stringify({ name: "Austen" }),
})

This verbose signature is good as there are additional properties we may want to add like the meta property etc as the gateway matures. serverless/event-gateway#179

However, it may be nice to offer a shorthand signature in some cases where all you're doing is sending events with some simple data payload.

gateway.emit('some.event.type', {
  'i': 'am data'
})

Function handlers discussion

Having a "standard" function handler for serverless function provides following benefits:

  • no need to change code when deploying function to different providers,
  • providing common logic (like e.g. decrypting secrets from KMS),
  • keeping things simple.

Research

AWS Lambda

One type of function handler:

exports.myHandler = function(event, context, callback) {
   ...
}
  • event – AWS Lambda uses this parameter to pass in event data to the handler.
  • context – AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. For more information, see The Context Object (Node.js).
  • callback – You can use the optional callback to return information to the caller, otherwise return value is null. For more information, see Using the Callback Parameter.

In case of API Gateway event specified structure and specified structure is expected in callback.

http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html

Google Cloud Functions

Two types of function handler, one for HTTP functions, another for background functions. HTTP function handler accepts express-like arguments (req & res).

exports.helloHttp = function helloHttp (req, res) {
  res.send(`Hello ${req.body.name || 'World'}!`);
};

https://cloud.google.com/functions/docs/writing/http

Background function handler accepts event and callback arguments.

exports.helloBackground = function helloBackground (event, callback) {
  callback(null, `Hello ${event.data.name || 'World'}!`);
};

https://cloud.google.com/functions/docs/writing/background

Azure Function

One type of function handler:

module.exports = function(context) {
    // function logic goes here :)
};

context object differes between regular functions and functions triggered by HTTP request.

In case of regular functions context object has a bindings property with myInput & myOutput which can be used for getting input args and setting result.

In case of HTTP func there are req & res objects under context.

https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-node

Proposition

We should provide two handlers. One generic handler and one for HTTP triggered functions.

Generic handler

In case of generic handler I would follow AWS Lambda definition. But as we want to abstract provider specific stuff, context object would be different that context object from AWS Lambda. Still, raw AWS Lambda context object should be accessible under e.g. AWSContext key in stdlib's context. As context object in most case it not needed in handler logic (and it doesn't occur in GCF) it's an optional argument.

handler(function(event, [context], callback){}) (or λ(...))

Arguments:

  • function(event, [context], callback) - hander function. Function accepts following arguments:
    • event - any type - event payload,
    • context (optional) - object - additional info
    • callback - function - callback function result callback(err, result).
const stdlib = require('@serverless/stdlib')();

exports.userFunc = stdlib.handler((event, context, callback) => {
  callback(new Error('not implemented'))  
});

// or

exports.userFunc = stdlib.λ((event, context, callback) => {
  callback(new Error('not implemented'))
});

HTTP handler

http is a handler for HTTP triggered functions. This handler takes function that accepts two arguments (express-like req & res)

http(function(event, [context], callback){})

Arguments:

Example:

const stdlib = require('@serverless/stdlib')();

exports.userFunc = stdlib.http((req, res) => {
  res.status(200).end();
});

cc @nikgraf @pmuens @ac360 @eahefnawy @DavidWells

call multiple functions method

call method should allow calling multiple functions at the same time. name parameter should accept both string and array (similar to async.paralell).

Example:

sdk.call([‘validateEmail’, ‘validateName’], user, function (err, results) {})

In case of calling multiple functions callback results object has keys corresponding to names of called functions.

Example:

sdk.call([‘validateEmail’, ‘validateName’], user, function (err, result) {
// result == {validateEmail: true, validateName: false}
})

In case of error in one of the functions err is returned and calls to other functions are discarded.

Naming is too verbose.

Would be nice if some of the naming was less verbose and kept a bit more simple.
createEventGatewayClient -> gateway
createSubscription -> subscribe
removeSubscription -> unsubscribe

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.