Discussion: Running Bolt apps on Function-as-a-Service
See original GitHub issueDescription
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: https://github.com/slackapi/bolt/issues/353#issuecomment-568811051
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 Promise
s.
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 https://github.com/slackapi/bolt/issues/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.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:9
- Comments:6 (5 by maintainers)
If it helps at all I had some success running Bolt on Lambda in this app by using the express receiver and
aws-serverless-express
. It seems to work as intended but has not been extensively tested.@threesquared Thanks for your comment. Yes, for simple apps, it works mostly. If a Bolt app has time-consuming
Promise
s, those operations may be unexpectedly terminated. The current Bolt implementation doesn’t offer a proper way to deal with the problem.