swift-aws-lambda-runtime icon indicating copy to clipboard operation
swift-aws-lambda-runtime copied to clipboard

Improves Developer Ergonomics for LambdaHandler Conformances

Open hectormatos2011 opened this issue 2 years ago • 18 comments

This PR aims to improve developer ergonomics for simple LambdaHandler conformances when trying to quickly put together a local server to test against.

Motivation:

Swift 5.7 added primary associated types for protocols where you can start using generic bracket syntax for protocols. An issue for this was opened not too long ago in this repo (#266) with the primary motivation of improving developer ergonomics around types conforming to LambdaHander. However, swift's type inference system should be able to handle the aforementioned issue just fine without using primary associated types (although support for that could still be useful when defining properties conforming to LambdaHandler when you want to constrain the types).

Swift's type inference system does not pick up the Event and Output types for objects conforming to LambdaHandler because those types are defined in the covariant protocol EventLoopLambdaHandler. This means that you need to add type aliases for Event and Output when conforming to LambdaHandler - resulting in less than ideal developer ergonomics when trying to spin up a quick little local server.

Additionally, the initializer for LambdaHandler is currently a required function in the protocol. This is super useful for setup logic for your Handlers but degrades the developer ergonomics of the most simple/minimal use-case for LambdaHandlers. Developers should be able to continue having the capability of writing simple lambda handlers like they could do in 0.5.2:

import AWSLambdaRuntime
import Foundation

Lambda.run { (context, input: Request, callback: @escaping (Result<String, Error>) -> Void) in
    callback(.success(...))
}

The current state of affairs in comparison is to explicitly define the Event and Output associated types of LambdaHandler when writing a simple lambda as well as being forced to include an init that isn't necessarily always required to successfully run a simple lambda:

import AWSLambdaRuntime
import Foundation

@main
struct EntryHandler: LambdaHandler {
    typealias Event = SomeCodableEventType
    typealias Output = SomeCodableOutputType
   
    init(context: AWSLambdaRuntimeCore.LambdaInitializationContext) async throws {
        // The body of this could be blank and the lambda would still work
    }
   
    func handle(_ event: Event, context: LambdaContext) async throws -> Output {
        // lambda logic
    }
}

With the changes on main, a developer loses the ease of writing a three-line lambda that they could do in 0.5.2.

On top of all of this, when needing to run a server locally in Xcode, a developer would need to add an environment variable to their local scheme. This isn't a much used feature in Xcode for developers coming from iOS who are looking to maybe start writing swift on the server. This isn't too big of a deal, but could be a bit of pain to do when trying to set yourself up with Lambda for the first time. It also introduces the possibility of having to manage two different environment variables if you're switching between debugging in Xcode or running the server locally in your Terminal. It would be nice to have the capability of defining this in code so a developer wouldn't have to open a separate window to toggle a server running locally.

I may be wrong about this (not a lot of experience writing swift on the server), but if a developer is running a local server in Xcode, it should be safe to assume that the server isn't running on an environment like AWS Lambda or Docker. I think that if a server is running their Lambda in Xcode, LOCAL_LAMBDA_SERVER_ENABLED should be enabled by default. Doing so would enable developers to package ready-to-go example local servers with their example mobile apps (maybe they want to demonstrate the usage of an SDK or something).

Currently, developers in this situation would have to instruct their users to set up this LOCAL_LAMBDA_SERVER_ENABLED environment variable. If a user is a swift developer already, they might have the urge to open that Package in Xcode and just hit run without knowing they need to set that variable up.

With the support of a variable for enabling running as a local server in Lambda directly, a developer could package a ready-to-go local server package with their example apps that would only require opening the package and running in Xcode with no extra set up.

Modifications:

  • Removes ambiguities between LambdaHandler, EventLoopLambdaHandler, and ByteBufferLambdaHandler. The handle(_:context:) functions shared across all three prevented the swift compiler from properly inferring the Event and Output types between LambdaHandler and EventLoopLambdaHandler. The handle(_:context:) functions are now more descriptive with their primary parameter to match the protocol they are associated with:
    • LambdaHandler.handle(_:context) has now been changed to LambdaHandler(request:context:)
    • EventLoopLambdaHandler.handle(_:context) has now been changed to EventLoopLambdaHandler(event:context:)
    • ByteBufferLambdaHandler.handle(_:context) has now been changed to ByteBufferLambdaHandler(buffer:context:)
  • Adds an init function to the LambdaHandler protocol
    • Adds a default implementation for init(context:) that calls through to init() - this allows fully qualified structs conforming to LambdaHandler to omit their initializers if no setup is required for the Lambda handler
  • Adds a static var isLocalServerEnabled to the ByteBufferLambdaHandler protocol
    • Default is true when running in Xcode, but still respects the LOCAL_LAMBDA_SERVER_ENABLED environment variable.
    • This defaults to false if running outside of Xcode
  • Updates tests with new changes

Result:

With all of the changes in this PR, a developer could package a local lambda handler Swift Package with an example app that could look like this:

@main
final class EntryHandler: LambdaHandler {
    func handle(request: YourDecodableRequest, context: LambdaContext) -> YourEncodableResponse {
        // lambda logic
    }
}

All previous logic is still retained but the optional code needed is now opt-in with sensible defaults.

hectormatos2011 avatar Sep 30 '22 17:09 hectormatos2011

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

Can one of the admins verify this patch?

swift-server-bot avatar Sep 30 '22 17:09 swift-server-bot

thanks @hectormatos2011, @fabianfett and myself have been discussing some API changes to the main branch that should help address some of the points you bring up. should be able to put up a PR soon and then we can maybe see what else is missing and you can help get it over the finish line. wdyt?

tomerd avatar Oct 04 '22 22:10 tomerd

@tomerd I'd love to contribute! Thanks for taking a look. Would you like me to close this one? There's some good nuggets of code in here that should at least help with the type inference issue

hectormatos2011 avatar Oct 07 '22 15:10 hectormatos2011

lets keep it open and focus on landing https://github.com/swift-server/swift-aws-lambda-runtime/pull/273. then we can see what else we need to bring over

tomerd avatar Oct 07 '22 17:10 tomerd

hi @hectormatos2011 would you like to revisit this now that we have the 1.0 API in place?

tomerd avatar Mar 10 '23 18:03 tomerd

Let me take a look at 1.0 and close the PR if need be

hectormatos2011 avatar Mar 10 '23 18:03 hectormatos2011

Closing this PR since most of the improvements have been implemented in the 1.0 release!

@tomerd Is it currently possible to have code in the Lambda that makes it always run with LOCAL_LAMBDA_SERVER_ENABLED or do we still have to put that in the target itself? I'd like to be able to deploy example documentation that a developer could just copy and paste into Xcode without having to finagle with Edit Schemes or anything like that

hectormatos2011 avatar Mar 14 '23 18:03 hectormatos2011

@tomerd Is it currently possible to have code in the Lambda that makes it always run with LOCAL_LAMBDA_SERVER_ENABLED or do we still have to put that in the target itself? I'd like to be able to deploy example documentation that a developer could just copy and paste into Xcode without having to finagle with Edit Schemes or anything like that

that is not possible right now, to protect users from accidentally deploying the mock / debug server

tomerd avatar Mar 14 '23 22:03 tomerd