### Motivation: - We want to store different entities that are needed when executing a handler within the LambdaContext (Logger, EventLoop, ByteBufferAllocator, …) - Currently the LambdaRuntimeClient creates the LambdaContext. Having the LambdaContext with the Logger, EventLoop and ByteBufferAllocator be created from the LambdaRuntimeClient feels to me too much for me. - Conceptionally the Lambda control plane api call is “get next Invocation” (API naming) ### Changes: - LambdaRuntimeClient responds with an Invocation and does not use the LambdaContext at all anymore. - LambdaRunner creates the LambdaContext with the Invocation, Logger and EventLoop. - LambdaContext has been renamed to Lambda.Context - Lambda.Context is a class now, since it is conceptionally not a value type and might be passed around a lot - Lambda.Context properties `traceId`, `invokedFunctionArn`, `deadline` are not optional anymore since they will be always set when executing a lambda - Creating an Invocation can fail with LambdaRuntimeClientError.invocationMissingHeader(String), if non optional headers are not present - the test MockLambdaServer and the performance test MockServer always return headers for deadline, traceId and function arn (static for now – could be changed with Behaviour flag?!) ### Open ends: - we will need to build some kind of Deadline into the context (See also #9 - probably for a different PR) - we have a stupid mapping between ByteBuffer and [UInt8] in the LambdaRunner for now (marked with two TODOs). I don’t want to change this in this PR since it will lead to huge merge conflicts down the road with the potentiall API changes we have in mind.
Swift AWS Lambda
This library is designed to simplify implementing an AWS Lambda using the Swift programming language.
Getting started
- Create a SwiftPM project and pull SwiftAwsLambda as dependency into your project
// swift-tools-version:5.0
import PackageDescription
let package = Package(
name: "my-lambda",
products: [
.executable(name: "MyLambda", targets: ["MyLambda"]),
],
dependencies: [
.package(url: "https://github.com/swift-server/swift-aws-lambda.git", .upToNextMajor(from: "0.1.0")),
],
targets: [
.target(name: "MyLambda", dependencies: ["SwiftAwsLambda"]),
]
)
- Create a main.swift and implement your Lambda. Typically a Lambda is implemented as a closure. For example, a simple closure that receives a string payload and replies with the reverse version:
import SwiftAwsLambda
// in this example we are receiving and responding with strings
Lambda.run { (context, payload: String, callback) in
callback(.success(String(payload.reversed())))
}
Or more typically, a simple closure that receives a json payload and replies with a json response via Codable:
private struct Request: Codable {}
private struct Response: Codable {}
// in this example we are receiving and responding with codables. Request and Response above are examples of how to use
// codables to model your reqeuest and response objects
Lambda.run { (_, _: Request, callback) in
callback(.success(Response()))
}
See a complete example in SwiftAwsLambdaSample.
- Deploy to AWS Lambda. To do so, you need to compile your Application for EC2 Linux, package it as a Zip file, and upload to AWS. You can find sample build and deployment scripts in SwiftAwsLambdaSample.
Architecture
The library supports three types of Lambdas:
[UInt8](byte array) based (default): seeSwiftAwsLambdaExampleStringbased: seeSwiftAwsLambdaStringExampleCodablebased: seeSwiftAwsLambdaCodableExample. This is the most pragmatic mode of operation, since AWS Lambda is JSON based.
The library is designed to integrate with AWS Lambda Runtime Engine, via the BYOL Native Runtime API. The latter is an HTTP server that exposes three main RESTful endpoint:
/runtime/invocation/next/runtime/invocation/response/runtime/invocation/error
The library encapsulates these endpoints and the expected lifecycle via LambdaRuntimeClient and LambdaRunner respectively.
Single Lambda Execution Workflow
- The library calls AWS Lambda Runtime Engine
/nextendpoint to retrieve the next invocation request. - The library parses the response HTTP headers and populate the
LambdaContextobject. - The library reads the response body and attempt to decode it, if required. Typically it decodes to user provided type which extends
Decodable, but users may choose to write Lambdas that receive the input asStringor[UInt8]byte array which require less, or no decoding. - The library hands off the
ContextandRequestto the user provided handler on a dedicatedDispatchqueue, providing isolation between user's and the library's code. - User's code processes the request asynchronously, invoking a callback upon completion, which returns a result type with the
ResponseorErrorpopulated. - In case of error, the library posts to AWS Lambda Runtime Engine
/errorendpoint to provide the error details, which will show up on AWS Lambda logs. - In case of success, the library will attempt to encode the response, if required. Typically it encodes from user provided type which extends
Encodable, but users may choose to write Lambdas that return aStringor[UInt8]byte array, which require less, or no encoding. The library then posts to AWS Lambda Runtime Engine/responseendpoint to provide the response.
Lifecycle Management
AWS Runtime Engine controls the Application lifecycle and in the happy case never terminates the application, only suspends it's execution when no work is avaialble. As such, the library main entry point is designed to run forever in a blocking fashion, performing the workflow described above in an endless loop. That loop is broken if/when an internal error occurs, such as a failure to communicate with AWS Runtime Engine API, or under other unexpected conditions.