You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is a proposal for a new feature, "Lazy Listener Functions" in Bolt for Python. This is a new feature for advanced developers that want to build serious Slack apps using Function as a Service. Adding this feature doesn't prevent most developers from using Bolt as with Bolt JS and Bolt Java. If this feature is greatly successful for many users, we may implement the same concept in other Bolt frameworks in the future.
Motivation
The primary motivation of this feature is to realize a more handy way to run asynchronous code after acknowledging incoming requests from Slack within 3 seconds.
When I was thinking about possible solutions for FaaS support in Bolt for JS, I came up with a "two-phase app" concept.
However, I think it was not simple enough, and the concept was far different from the original Bolt framework.
Lazy listener functions are more powerful and simpler than that approach. This mechanism enables developers to more easily manage async operations, even in FaaS environments.
API Design
Let's say you have the following slash command listener. Of course, this works without any issues.
fromslack_boltimportAppapp=App()
command="/start-long-task"@app.command(command)defdo_everything_here(payload, ack, respond, client):
ifpayload.get("text", None) isNone:
ack(f":x: Usage: {command} (task name here)")
else:
task_name=payload["text"]
ack(f"Accepted! (task: {task_name})") # This immediately sends 200 OK to Slack# start a long tasktime.sleep(5) # Doing something meaningful takes timetask_name=payload["text"]
respond(f"Completed! (task: {task_name}, url: https://example.com/result)")
With Lazy listener functions, the same code can be as below. All you need to do is give two types of args, ack and lazy, to a listener. By using this interface, you can separate the acknowledgment phase and long operation in a concise way.
ack is responsible for returning an immediate HTTP response to Slack API servers within 3 seconds. By contrast, lazy functions are not supposed to return any response. They can do anything by leveraging all the listener args apart from ack() utility. Also, they are completely free from 3-second timeouts.
As the lazy is a list, you can set multiple lazy functions to a single listener. The lazy functions will be executed in parallel.
defacknowledge_anyway(ack):
ack()
defopen_modal(payload, client):
api_response=client.views_open(trigger_id=payload["trigger_id"], view={ })
defstart_background_job(payload):
# do a long taskpassapp.command("/data-request")(
ack=acknowledge_anyway,
lazy=[open_modal, start_background_job],
)
Mechanism
For complex apps, this mechanism can improve code readability. But the biggest benefit of this API is not readability. This mechanism provides better flexibility for choosing the runtime to execute asynchronous operations.
To support Lazy Listener Functions, the App/AsyncApp class should have a LazyListenerRunner internally. The component manages and runs lazy functions. Its start method kicks an async operation with the indication of recursive execution. The run method runs the lazy function with given payload and respond and say utilities.
classLazyListenerRunner:
# Bolt runs this method when starting a new async operation# This method copies the payload and sets the lazy function's information to it.# The duplicated request has lazy_only: True and lazy_function_name: str.defstart(self, lazy_function: Callable[..., None], request: BoltRequest) ->None:
pass# This method can be executed in any of a different thread, Future, and a different runtime# In the case of AWS Lambda, this method is supposed to be invoked in a different Lambda container.defrun(self, lazy_function: Callable[..., None], request: BoltRequest) ->None:
pass
LazyListenerRunner submits a new async execution to its runtime. The runtime to use is fully customizable. It can be a different thread, asyncio's Future, different server/container, and whatever you prefer.
The out-of-the-box AWS Lambda adapter takes advantage of it.
classLambdaLazyListenerRunner(LazyListenerRunner):
def__init__(self):
self.lambda_client=boto3.client("lambda")
defstart(self, function: Callable[..., None], request: BoltRequest) ->None:
event: dict=request.context["lambda_request"] # duplicated original requestheaders=event["headers"]
headers["x-slack-bolt-lazy-only"] ="1"# set lazy execution's specific headerheaders["x-slack-bolt-lazy-function-name"] =request.lazy_function_name# the function to run laterevent["method"] ="NONE"invocation=self.lambda_client.invoke(
FunctionName=request.context["aws_lambda_function_name"],
InvocationType="Event",
Payload=json.dumps(event),
)
Runners can rely on either of request.lazy_only / request.lazy_function_name (when sharing memory) or two x-slack-bolt- headers in recursive requests (when submitting a new request - these are converted to request.lazy_only and request.lazy_fucntion_name inside Bolt).
"x-slack-bolt-lazy-only" header value -> request.lazy_only: bool
"x-slack-bolt-lazy-function-name" header value -> request.lazy_function_name: Optional[str]
When Bolt runs App#dispatch (or AsyncApp#async_dispatch) method recursively, request.lazy_only should be True and request.lazy_function_name should exist. In the case, ack function and other lazy functions will be skipped.
Next Steps
I've already implemented the initial version of this feature. I will come up with a WIP (Work In Progress) pull request and am happy to hear feedback from other maintainers and the communities!
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.
This is a proposal for a new feature, "Lazy Listener Functions" in Bolt for Python. This is a new feature for advanced developers that want to build serious Slack apps using Function as a Service. Adding this feature doesn't prevent most developers from using Bolt as with Bolt JS and Bolt Java. If this feature is greatly successful for many users, we may implement the same concept in other Bolt frameworks in the future.
Motivation
The primary motivation of this feature is to realize a more handy way to run asynchronous code after acknowledging incoming requests from Slack within 3 seconds.
When I was thinking about possible solutions for FaaS support in Bolt for JS, I came up with a "two-phase app" concept.
However, I think it was not simple enough, and the concept was far different from the original Bolt framework.
Lazy listener functions are more powerful and simpler than that approach. This mechanism enables developers to more easily manage async operations, even in FaaS environments.
API Design
Let's say you have the following slash command listener. Of course, this works without any issues.
With Lazy listener functions, the same code can be as below. All you need to do is give two types of args,
ack
andlazy
, to a listener. By using this interface, you can separate the acknowledgment phase and long operation in a concise way.ack
is responsible for returning an immediate HTTP response to Slack API servers within 3 seconds. By contrast,lazy
functions are not supposed to return any response. They can do anything by leveraging all the listener args apart fromack()
utility. Also, they are completely free from 3-second timeouts.As the
lazy
is a list, you can set multiple lazy functions to a single listener. The lazy functions will be executed in parallel.Mechanism
For complex apps, this mechanism can improve code readability. But the biggest benefit of this API is not readability. This mechanism provides better flexibility for choosing the runtime to execute asynchronous operations.
To support Lazy Listener Functions, the
App
/AsyncApp
class should have aLazyListenerRunner
internally. The component manages and runs lazy functions. Itsstart
method kicks an async operation with the indication of recursive execution. Therun
method runs the lazy function with given payload andrespond
andsay
utilities.LazyListenerRunner
submits a new async execution to its runtime. The runtime to use is fully customizable. It can be a different thread, asyncio's Future, different server/container, and whatever you prefer.The out-of-the-box AWS Lambda adapter takes advantage of it.
Runners can rely on either of request.lazy_only / request.lazy_function_name (when sharing memory) or two
x-slack-bolt-
headers in recursive requests (when submitting a new request - these are converted to request.lazy_only and request.lazy_fucntion_name inside Bolt).When Bolt runs App#dispatch (or AsyncApp#async_dispatch) method recursively, request.lazy_only should be True and request.lazy_function_name should exist. In the case, ack function and other lazy functions will be skipped.
Next Steps
I've already implemented the initial version of this feature. I will come up with a WIP (Work In Progress) pull request and am happy to hear feedback from other maintainers and the communities!
Requirements (place an
x
in each of the[ ]
)The text was updated successfully, but these errors were encountered: