Skip to content

Consider Alternate Handler Types #96

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
davidbarsky opened this issue Mar 18, 2019 · 2 comments · Fixed by #401
Closed

Consider Alternate Handler Types #96

davidbarsky opened this issue Mar 18, 2019 · 2 comments · Fixed by #401
Labels
enhancement New feature or request

Comments

@davidbarsky
Copy link
Contributor

davidbarsky commented Mar 18, 2019

(Pulled from #94)

I wonder if we can support that through an optional extension for customers that do care about this. To that end, I've been talking to @carllerche—he might be able to comment on this more—about the usage of Tower in underlying of the Lambda runtime, and my current thinking is that we can provide several valid handler signatures as implemented by specialized tower services and a corresponding Into<Service> (for context, this is the foundational Service trait) that'll be executed by the the Lambda runtime itself. Given this definition, we can provide several specialized services along the lines of:

  • Service<(http::Request<T>, Context)> -> http::Response<U>
  • Service<(http::Request<T>, Context)> -> Result<http::Response<U>, HandlerError>
  • Service<(aws_lambda_events::event::sqs::SqsEvent, Context)> -> U
  • Service<(aws_lambda_events::event::sqs::SqsEvent, Context)> -> Result<U, HandlerError>

Note: each service function signature would be implemented in terms of a sealed trait similar to HttpService.

In the absence of a handler error, the Into<Service> implementation would place a default value for HandlerError. Handler construction will still be available using a something similar to a ServiceFn.

To address some potential concerns:

  • We might be able to cut down on our compile times by using more, smaller crates and disabling some of the chunkier features in Tokio, Hyper, and by removing our usage of Failure.
  • The internals are more easily testable using tower-mock.
  • We can provide support for Tide's HttpService behind a feature flag.
@davidbarsky davidbarsky assigned sapessi and unassigned sapessi Mar 18, 2019
@davidbarsky
Copy link
Contributor Author

cc: @softprops, @sapessi.

@davidbarsky davidbarsky changed the title Consider Alternernate Handler Types Consider Alternate Handler Types Mar 18, 2019
@carllerche
Copy link

As a disclaimer, I am not very familiar with lambda itself.

To provide some context re: Tower. Tower is not a framework, or even tied to a specific protocol. It aims to provide a standardized handler function. The goal is to avoid having each framework / lib provide its own handler function so that there is a common base upon which we can build things.

For example, in the lambda runtime lib, you define:

pub trait Handler<Event, Output, EventError> {
    /// Method to execute the handler function
    fn run(&mut self, event: Event, ctx: Context) -> Result<Output, EventError>;
}

Tower defines the Service trait (the equivalent of Handler) as:

pub trait Service<Request> {
    type Response;
    type Error;
    type Future: Future<Item = Self::Response, Error = Self::Error>;

    fn poll_ready(&mut self) -> Poll<(), Self::Error>;

    fn call(&mut self, req: Request) -> Self::Future;
}

A few thoughts:

Asynchronous

Tower Service is an asynchronous handler. It looks like lambda is currently synchronous and is only able to handle one concurrent request at a time. It looks like you already use Tokio to implement the interface to the lambda infrastructure (the client component). It also looks like you run the client in separate threads (via hyper client) and use synchronization to bridge the handler thread w/ the client thread(s).

If you opted for an async handler (like tower Service), you would be able to avoid most of the overhead and run on a single thread with no synchronization. You would also be able to process requests concurrently.

It also is farily trivial to bridge async -> sync by using a thread pool. In fact, this sounds like a great combinator for Tower to include.

Light & Stable

The tower-service crate is light and stable. The only dependency it has is on the futures crate. After Future is stable in std, tower-service will have no dependencies. The change from futures 0.1 to std::Future has a high probability of being the only breaking change.

The tower-service crate itself only contains a single trait. All shared utilities are external.

Backpressure

The Tower service trait comes with a mechanism by which to implement backpressure: the poll_ready function. Implementing back pressure is required in any system to avoid infinite queuing. Having a back pressure strategy built into the core trait is important to make sure that each layer of the stack opts in.

We experimented a lot with strategies by which to expose backpressure. I believe that the strategy we landed on works very well in many different situations. If you want to talk more about this, I would be happy to.

Common interface

Like I said, Tower is not a web framework, or even tied to a specific protocol. It is a common interface that can be implemented by any other web framework / client. By providing a common interface, adapters between components are no longer needed. If every library / framework provides its own Handler trait, users will be required to write bridges to make anything work.

Also, by defining a common interface, it becomes possible to build reusable middleware components. You can see the tower project provides a number of common components: https://github.com/tower-rs/tower.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants