Skip to content

Discussion: Running Bolt apps on Function-as-a-Service #361

@seratch

Description

@seratch

Description

Disclaimer

Creating this issue doesn't mean the Bolt team is going to provide a proper way to support FaaS (e.g., AWS Lambda, Google Cloud Functions, Cloud Functions for Firebase, etc.) in the short run.

I wanted to create a place to discuss any future possibilities to support FaaS in some ways.

The challenge we have with FaaS

One limitation you should know when you run Bolt apps on FaaS is that any asynchronous operations can be silently terminated once its internet-facing function responds to an incoming HTTP request from Slack.

Examples of "asynchronous operations" here are say, respond, app.client calls, and whatever internally starts Promise operations separately from ack() completion.

Let's say you want to create an AWS Lambda function that is free from Slack's 3-second timeouts and the limitation I mentioned above. In this case, the following code is a solution.

const { App, ExpressReceiver } = require('@slack/bolt');
import AWS = require('aws-sdk');

const receiver = new ExpressReceiver({
  signingSecret: process.env.SLACK_SIGNING_SECRET
});
const app = new App({
  receiver,
  token: process.env.SLACK_BOT_TOKEN,
});

app.command("/hello", ({ body, ack }) => {
  if (isLocalDevelopment()) {
    ack();
    mainLogic(body);
  } else {
    const lambda = new AWS.Lambda();
    const params: AWS.Lambda.InvocationRequest = {
      InvocationType: 'Event', // async invocation
      FunctionName: functionName,
      Payload: JSON.stringify(body)
    };
    const lambdaInvocation = await lambda.invoke(params).promise();
    // check the lambdaInvocation here
    ack();
  }
});

function mainLogic(body) {
  // do something it may take a long time
}

// frontend: internet-facing function handling requests from Slack
const awsServerlessExpress = require('aws-serverless-express');
const server = awsServerlessExpress.createServer(receiver.app);
module.exports.frontend = (event, context) => {
  awsServerlessExpress.proxy(server, event, context);
}

// backend: internal function for anything apart from acknowledging requests from Slack
module.exports.backend = async function (event, _context) {
  // if you reuse this function for other patterns, need to dispatch the events
  await mainLogic(event);
  return {
    statusCode: 200,
    body: JSON.stringify({ message: 'done' }),
  };
};

One possible idea I came up with

Let me refer to my comment in another issue: #353 (comment)

One feasible idea is creating a FaaS support package as a 3rd-party one. That means the package won't be managed by @slackapi. The authors of such packages can be anyone. If someone starts such projects, I'll be more than happy to support them.

As a first step, I'll publish my prototype implementation in my own repository and will start a new GitHub issue in this repository. I hope it will be a good starting point for people interested in FaaS support. Even though my prototype is still in very alpha quality, checking it could be helpful for the folks that may start new projects.

If Bolt changes its APIs and internals, it may be feasible to have a 3rd-party generalized npm package that offers proper FaaS supports for Bolt.

Here is my prototype demonstrating it: https://github.com/seratch/bolt-aws-lambda-proof-of-concept
It doesn't support all the features yet but it just works for supported cases.

Here is a simple example code (just pasted from the repository's README).

const app = new TwoPhaseApp({
  token: process.env.SLACK_BOT_TOKEN,
  // this receiver tries to get AWS credentials from env variables by default
  receiver: new AwsLambdaReceiver({
    signingSecret: process.env.SLACK_SIGNING_SECRET
  })
});

app.command('/lambda')
  .ack(({ ack }) => {
    // phase1 function: compatible with current listener function
    ack('ack response');
  })
  .then(async ({ body, say }) => {
    // phase2 function: this one is invoked as another lambda function
    return say('How are you?').then(() => say("I'm good!"));
  });

Now developers don't need to directly use AWS Lambda SDK. AwsLambdaReceiver does everything for you: https://github.com/seratch/bolt-aws-lambda-proof-of-concept/blob/7b72a5e416977036e98c4bfbf40ee0567910766c/src/added/AwsLambdaReceiver.ts#L174-L189

Apart from the things specific to AWS, this approach is applicable to any others (not only FaaS).

In this design, the two phases are:

  • Phase 1: internet-facing handler - responsible for request acknowledgment and synchronous things (e.g., signature verification, dispatching requests, payload validation, and responding a message as part of HTTP responses)
  • Phase 2: internal function - can do anything with the relayed request body asynchronously / use respond to send a message to a user asynchronously

Phase 2 function is supposed to return a single Promise as its result. The design makes easier to write code in idiomatic ways (like using then/catch and/or Promise.all) for working with Promises.

Next steps

If someone is interested in starting with my PoC prototype to build a 3rd-party library, I'm happy to donate my code (it's under the MIT license) and will be more than happy to contribute to it as an individual.

To realize such 3rd parties, a few changes on the Bolt side are still needed. @aoberoi shared a related proposal at #353. Join the conversation to share your thoughts and/or feedback.

What type of issue is this? (place an x in one of the [ ])

  • bug
  • enhancement (feature request)
  • question
  • documentation related
  • testing related
  • discussion

Requirements (place an x in each of the [ ])

  • I've read and understood the Contributing guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've searched for any related issues and avoided creating a duplicate issue.

Metadata

Metadata

Assignees

Labels

discussionM-T: An issue where more input is needed to reach a decision

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions