-
Notifications
You must be signed in to change notification settings - Fork 113
Lambda factory as a protocol requirement. #244
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
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,27 +24,6 @@ import NIOCore | |
import NIOPosix | ||
|
||
public enum Lambda { | ||
public typealias Handler = ByteBufferLambdaHandler | ||
|
||
/// `ByteBufferLambdaHandler` factory. | ||
/// | ||
/// A function that takes a `InitializationContext` and returns an `EventLoopFuture` of a `ByteBufferLambdaHandler` | ||
public typealias HandlerFactory = (InitializationContext) -> EventLoopFuture<Handler> | ||
|
||
/// Run a Lambda defined by implementing the `LambdaHandler` protocol provided via a `LambdaHandlerFactory`. | ||
/// Use this to initialize all your resources that you want to cache between invocations. This could be database connections and HTTP clients for example. | ||
/// It is encouraged to use the given `EventLoop`'s conformance to `EventLoopGroup` when initializing NIO dependencies. This will improve overall performance. | ||
/// | ||
/// - parameters: | ||
/// - factory: A `ByteBufferLambdaHandler` factory. | ||
/// | ||
/// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. | ||
public static func run(_ factory: @escaping HandlerFactory) { | ||
if case .failure(let error) = self.run(factory: factory) { | ||
fatalError("\(error)") | ||
} | ||
} | ||
|
||
/// Utility to access/read environment variables | ||
public static func env(_ name: String) -> String? { | ||
guard let value = getenv(name) else { | ||
|
@@ -53,30 +32,23 @@ public enum Lambda { | |
return String(cString: value) | ||
} | ||
|
||
#if compiler(>=5.5) && canImport(_Concurrency) | ||
// for testing and internal use | ||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) | ||
internal static func run<Handler: LambdaHandler>(configuration: Configuration = .init(), handlerType: Handler.Type) -> Result<Int, Error> { | ||
self.run(configuration: configuration, factory: { context -> EventLoopFuture<ByteBufferLambdaHandler> in | ||
let promise = context.eventLoop.makePromise(of: ByteBufferLambdaHandler.self) | ||
promise.completeWithTask { | ||
try await Handler(context: context) | ||
} | ||
return promise.futureResult | ||
}) | ||
} | ||
#endif | ||
|
||
// for testing and internal use | ||
internal static func run(configuration: Configuration = .init(), factory: @escaping HandlerFactory) -> Result<Int, Error> { | ||
let _run = { (configuration: Configuration, factory: @escaping HandlerFactory) -> Result<Int, Error> in | ||
/// Run a Lambda defined by implementing the ``ByteBufferLambdaHandler`` protocol. | ||
/// The Runtime will manage the Lambdas application lifecycle automatically. It will invoke the | ||
/// ``ByteBufferLambdaHandler/factory(context:)`` to create a new Handler. | ||
/// | ||
/// - parameters: | ||
/// - factory: A `ByteBufferLambdaHandler` factory. | ||
/// | ||
/// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. | ||
internal static func run<Handler: ByteBufferLambdaHandler>(configuration: Configuration = .init(), handlerType: Handler.Type) -> Result<Int, Error> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess needed for the generics? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. It is there to make the user declare the Generic type. Otherwise the user would need to write: Lambda.run<Handler>() |
||
let _run = { (configuration: Configuration) -> Result<Int, Error> in | ||
Backtrace.install() | ||
var logger = Logger(label: "Lambda") | ||
logger.logLevel = configuration.general.logLevel | ||
|
||
var result: Result<Int, Error>! | ||
MultiThreadedEventLoopGroup.withCurrentThreadAsEventLoop { eventLoop in | ||
let runtime = LambdaRuntime(eventLoop: eventLoop, logger: logger, configuration: configuration, factory: factory) | ||
let runtime = LambdaRuntime<Handler>(eventLoop: eventLoop, logger: logger, configuration: configuration) | ||
#if DEBUG | ||
let signalSource = trap(signal: configuration.lifecycle.stopSignal) { signal in | ||
logger.info("intercepted signal: \(signal)") | ||
|
@@ -108,13 +80,13 @@ public enum Lambda { | |
if Lambda.env("LOCAL_LAMBDA_SERVER_ENABLED").flatMap(Bool.init) ?? false { | ||
do { | ||
return try Lambda.withLocalServer { | ||
_run(configuration, factory) | ||
_run(configuration) | ||
} | ||
} catch { | ||
return .failure(error) | ||
} | ||
} else { | ||
return _run(configuration, factory) | ||
return _run(configuration) | ||
} | ||
#else | ||
return _run(configuration, factory) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,13 @@ import NIOCore | |
// MARK: - LambdaHandler | ||
|
||
#if compiler(>=5.5) && canImport(_Concurrency) | ||
/// Strongly typed, processing protocol for a Lambda that takes a user defined `Event` and returns a user defined `Output` async. | ||
/// Strongly typed, processing protocol for a Lambda that takes a user defined | ||
/// ``EventLoopLambdaHandler/Event`` and returns a user defined | ||
/// ``EventLoopLambdaHandler/Output`` asynchronously. | ||
/// | ||
/// - note: Most users should implement this protocol instead of the lower | ||
/// level protocols ``EventLoopLambdaHandler`` and | ||
/// ``ByteBufferLambdaHandler``. | ||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) | ||
public protocol LambdaHandler: EventLoopLambdaHandler { | ||
/// The Lambda initialization method | ||
|
@@ -42,6 +48,14 @@ public protocol LambdaHandler: EventLoopLambdaHandler { | |
|
||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) | ||
extension LambdaHandler { | ||
public static func factory(context: Lambda.InitializationContext) -> EventLoopFuture<Self> { | ||
let promise = context.eventLoop.makePromise(of: Self.self) | ||
promise.completeWithTask { | ||
try await Self(context: context) | ||
} | ||
return promise.futureResult | ||
} | ||
|
||
public func handle(_ event: Event, context: LambdaContext) -> EventLoopFuture<Output> { | ||
let promise = context.eventLoop.makePromise(of: Output.self) | ||
promise.completeWithTask { | ||
|
@@ -51,25 +65,30 @@ extension LambdaHandler { | |
} | ||
} | ||
|
||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) | ||
extension LambdaHandler { | ||
public static func main() { | ||
_ = Lambda.run(handlerType: Self.self) | ||
} | ||
} | ||
#endif | ||
|
||
// MARK: - EventLoopLambdaHandler | ||
|
||
/// Strongly typed, `EventLoopFuture` based processing protocol for a Lambda that takes a user defined `Event` and returns a user defined `Output` asynchronously. | ||
/// `EventLoopLambdaHandler` extends `ByteBufferLambdaHandler`, performing `ByteBuffer` -> `Event` decoding and `Output` -> `ByteBuffer` encoding. | ||
/// Strongly typed, `EventLoopFuture` based processing protocol for a Lambda that takes a user | ||
/// defined ``Event`` and returns a user defined ``Output`` asynchronously. | ||
/// | ||
/// - note: To implement a Lambda, implement either `LambdaHandler` or the `EventLoopLambdaHandler` protocol. | ||
/// The `LambdaHandler` will offload the Lambda execution to a `DispatchQueue` making processing safer but slower | ||
/// The `EventLoopLambdaHandler` will execute the Lambda on the same `EventLoop` as the core runtime engine, making the processing faster but requires | ||
/// more care from the implementation to never block the `EventLoop`. | ||
/// ``EventLoopLambdaHandler`` extends ``ByteBufferLambdaHandler``, performing | ||
/// `ByteBuffer` -> ``Event`` decoding and ``Output`` -> `ByteBuffer` encoding. | ||
/// | ||
/// - note: To implement a Lambda, implement either ``LambdaHandler`` or the | ||
/// ``EventLoopLambdaHandler`` protocol. The ``LambdaHandler`` will offload | ||
/// the Lambda execution to an async Task making processing safer but slower (due to | ||
/// fewer thread hops). | ||
/// The ``EventLoopLambdaHandler`` will execute the Lambda on the same `EventLoop` | ||
/// as the core runtime engine, making the processing faster but requires more care from the | ||
/// implementation to never block the `EventLoop`. Implement this protocol only in performance | ||
/// critical situations and implement ``LambdaHandler`` in all other circumstances. | ||
public protocol EventLoopLambdaHandler: ByteBufferLambdaHandler { | ||
/// The lambda functions input. In most cases this should be Codable. If your event originates from an | ||
/// AWS service, have a look at [AWSLambdaEvents](https://github.com/swift-server/swift-aws-lambda-events), | ||
/// which provides a number of commonly used AWS Event implementations. | ||
associatedtype Event | ||
/// The lambda functions output. Can be `Void`. | ||
associatedtype Output | ||
|
||
/// The Lambda handling method | ||
|
@@ -135,9 +154,18 @@ extension EventLoopLambdaHandler where Output == Void { | |
|
||
/// An `EventLoopFuture` based processing protocol for a Lambda that takes a `ByteBuffer` and returns a `ByteBuffer?` asynchronously. | ||
/// | ||
/// - note: This is a low level protocol designed to power the higher level `EventLoopLambdaHandler` and `LambdaHandler` based APIs. | ||
/// - note: This is a low level protocol designed to power the higher level ``EventLoopLambdaHandler`` and | ||
/// ``LambdaHandler`` based APIs. | ||
/// Most users are not expected to use this protocol. | ||
public protocol ByteBufferLambdaHandler { | ||
/// Create your Lambda handler for the runtime. | ||
/// | ||
/// Use this to initialize all your resources that you want to cache between invocations. This could be database | ||
/// connections and HTTP clients for example. It is encouraged to use the given `EventLoop`'s conformance | ||
/// to `EventLoopGroup` when initializing NIO dependencies. This will improve overall performance, as it | ||
/// minimizes thread hopping. | ||
static func factory(context: Lambda.InitializationContext) -> EventLoopFuture<Self> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, generally "make" is the word to use; |
||
|
||
/// The Lambda handling method | ||
/// Concrete Lambda handlers implement this method to provide the Lambda functionality. | ||
/// | ||
|
@@ -163,6 +191,20 @@ extension ByteBufferLambdaHandler { | |
} | ||
} | ||
|
||
extension ByteBufferLambdaHandler { | ||
/// Initializes and runs the lambda function. | ||
/// | ||
/// If you precede your ``ByteBufferLambdaHandler`` conformer's declaration with the | ||
/// [@main](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID626) | ||
/// attribute, the system calls the conformer's `main()` method to launch the lambda function. | ||
/// | ||
/// The lambda runtime provides a default implementation of the method that manages the launch | ||
/// process. | ||
public static func main() { | ||
_ = Lambda.run(configuration: .init(), handlerType: Self.self) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we mark Lambda::run @discardableResult? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the |
||
} | ||
} | ||
|
||
@usableFromInline | ||
enum CodecError: Error { | ||
case requestDecoding(Error) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update docs ^^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed