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
Copy file name to clipboardExpand all lines: Sources/AWSLambdaRuntimeCore/Documentation.docc/Proposals/0001-v2-api.md
+30-30Lines changed: 30 additions & 30 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# v2 API proposal for swift-aws-lambda-runtime
2
2
3
-
`swift-aws-lambda-runtime` is an important library for the Swift on Server ecosystem. The initial API was written before async/await was introduced to Swift. When async/await was introduced, shims were added to bridge between the underlying SwiftNIO `EventLoop` interfaces and async/await. However, just like ``gRPC-swift`` and `postgres-nio`, we now want to shift to solely using async/await instead of `EventLoop` interfaces. For this, large parts of the current API have to be reconsidered.
3
+
`swift-aws-lambda-runtime` is an important library for the Swift on Server ecosystem. The initial API was written before async/await was introduced to Swift. When async/await was introduced, shims were added to bridge between the underlying SwiftNIO `EventLoop` interfaces and async/await. However, just like `gRPC-swift` and `postgres-nio`, we now want to shift to solely using async/await instead of `EventLoop` interfaces. For this, large parts of the current API have to be reconsidered.
4
4
5
5
## Motivation
6
6
@@ -18,13 +18,13 @@ A Lambda function can currently be implemented through conformance to the variou
18
18
19
19
The `SimpleLambdaHandler` protocol provides a quick and easy way to implement a basic Lambda function. It only requires an implementation of the `handle` function where the business logic of the Lambda function can be written. `SimpleLambdaHandler` is perfectly sufficient for small use-cases as the user does not need to spend much time looking into the library.
20
20
21
-
However, `SimpleLambdaHandler` cannot be used when services such as a database client need to be initalized before the Lambda runtime starts and then also gracefully shutdown prior to the runtime terminating. This is because the only way to register termination logic is through the `LambdaInitializationContext` (containing a field `terminator: LambdaTerminator`) which is created and used *internally* within `LambdaRuntime` and never exposed through `SimpleLambdaHandler`. For such use-cases, other handler protocols like `LambdaHandler` must be used. `LambdaHandler` exposes a `context` argument of type `LambdaInitializationContext` through its initializer. Within the initializer, required services can be initalized and their graceful shutdown logic can be registered with the `context.terminator.register` function.
21
+
However, `SimpleLambdaHandler` cannot be used when services such as a database client need to be initalized before the Lambda runtime starts and then also gracefully shutdown prior to the runtime terminating. This is because the only way to register termination logic is through the `LambdaInitializationContext` (containing a field `terminator: LambdaTerminator`) which is created and used _internally_ within `LambdaRuntime` and never exposed through `SimpleLambdaHandler`. For such use-cases, other handler protocols like `LambdaHandler` must be used. `LambdaHandler` exposes a `context` argument of type `LambdaInitializationContext` through its initializer. Within the initializer, required services can be initalized and their graceful shutdown logic can be registered with the `context.terminator.register` function.
22
22
23
23
Yet, `LambdaHandler` is quite cumbersome to use in such use-cases as users have to deviate from the established norms of the Swift on Server ecosystem in order to cleanly manage the lifecycle of the services intended to be used. This is because the convenient `swift-service-lifecycle` v2 library — which is commonly used for cleanly managing the lifecycles of required services and widely supported by many libraries — cannot be used in a structured concurrency manner.
24
24
25
25
#### Does not integrate well with swift-service-lifecycle in a structured concurrency manner
26
26
27
-
The Lambda runtime can only be started using the **internal**`Lambda.run()` function. This function is called by the `main()` function defined by the `LambdaHandler` protocol, preventing users from injecting initialized services into the runtime *prior* to it starting. As shown below, this forces users to use an **unstructured concurrency** approach and manually initialize services, leading to the issue of the user then perhaps forgetting to gracefully shutdown the initalized services:
27
+
The Lambda runtime can only be started using the **internal**`Lambda.run()` function. This function is called by the `main()` function defined by the `LambdaHandler` protocol, preventing users from injecting initialized services into the runtime _prior_ to it starting. As shown below, this forces users to use an **unstructured concurrency** approach and manually initialize services, leading to the issue of the user then perhaps forgetting to gracefully shutdown the initalized services:
In the current API, there are extensions and Codable wrapper classes for decoding events and encoding computed responses for *each* different handler protocol and for both `String` and `JSON` formats. This has resulted in a lot of boilerplate code which can very easily be made generic and simplified in v2.
66
+
In the current API, there are extensions and Codable wrapper classes for decoding events and encoding computed responses for _each_ different handler protocol and for both `String` and `JSON` formats. This has resulted in a lot of boilerplate code which can very easily be made generic and simplified in v2.
67
67
68
68
### New features
69
69
@@ -77,18 +77,18 @@ In May [AWS described in a blog post that you can run background tasks in Lambda
77
77
78
78
## Proposed Solution
79
79
80
-
####async/await-first API
80
+
### async/await-first API
81
81
82
82
Large parts of `Lambda`, `LambdaHandler`, and `LambdaRuntime` will be re-written to use async/await constructs in place of the `EventLoop` family of interfaces.
83
83
84
-
####Providing ownership of main() and support for swift-service-lifecycle
84
+
### Providing ownership of main() and support for swift-service-lifecycle
85
85
86
-
* Instead of conforming to a handler protocol, users can now create a `LambdaRuntime` by passing in a handler closure.
87
-
*`LambdaRuntime` conforms to `ServiceLifecycle.Service` by implementing a `run()` method that contains initialization and graceful shutdown logic.
88
-
* This allows the lifecycle of the `LambdaRuntime` to be managed with `swift-service-lifecycle`*alongside* and in the same way the lifecycles of the required services are managed, e.g. `try await ServiceGroup(services: [postgresClient, ..., lambdaRuntime], ...).run()`.
89
-
* Dependencies can now be injected into `LambdaRuntime` — `swift-service-lifecycle` guarantees that the services will be initialized *before* the `LambdaRuntime`’s `run()` function is called.
90
-
* The required services can then be used within the handler in a structured concurrency manner. `swift-service-lifecycle` takes care of listening for termination signals and terminating the services as well as the `LambdaRuntime` in correct order.
91
-
*`LambdaTerminator` can now be eliminated because its role is replaced with `swift-service-lifecycle`. The termination logic of the Lambda function will be implemented in the conforming `run()` function of `LambdaRuntime`.
86
+
- Instead of conforming to a handler protocol, users can now create a `LambdaRuntime` by passing in a handler closure.
87
+
-`LambdaRuntime` conforms to `ServiceLifecycle.Service` by implementing a `run()` method that contains initialization and graceful shutdown logic.
88
+
- This allows the lifecycle of the `LambdaRuntime` to be managed with `swift-service-lifecycle`_alongside_ and in the same way the lifecycles of the required services are managed, e.g. `try await ServiceGroup(services: [postgresClient, ..., lambdaRuntime], ...).run()`.
89
+
- Dependencies can now be injected into `LambdaRuntime` — `swift-service-lifecycle` guarantees that the services will be initialized _before_ the `LambdaRuntime`’s `run()` function is called.
90
+
- The required services can then be used within the handler in a structured concurrency manner. `swift-service-lifecycle` takes care of listening for termination signals and terminating the services as well as the `LambdaRuntime` in correct order.
91
+
-`LambdaTerminator` can now be eliminated because its role is replaced with `swift-service-lifecycle`. The termination logic of the Lambda function will be implemented in the conforming `run()` function of `LambdaRuntime`.
92
92
93
93
With this, the earlier code snippet can be replaced with something much easier to read, maintain, and debug:
94
94
@@ -112,13 +112,13 @@ let serviceGroup = ServiceGroup(
112
112
tryawait serviceGroup.run()
113
113
```
114
114
115
-
####Simplifying Codable support
115
+
### Simplifying Codable support
116
116
117
117
A detailed explanation is provided in the Codable Support section. In short, much of the boilerplate code defined for each handler protocol in `Lambda+Codable` and `Lambda+String` will be replaced with a single `LambdaCodableAdapter` class.
118
118
119
119
This adapter class is generic over (1) any handler conforming to `LambdaHandler`, (2) the user-specified input and output types, and (3) any decoder and encoder conforming to `LambdaEventDecoder` and `LambdaOutputDecoder`. The adapter will wrap the underlying handler with encoding/decoding logic.
120
120
121
-
####Simplifying the handler protocols
121
+
### Simplifying the handler protocols
122
122
123
123
There are four different handler protocols in the current API (`SimpleLambdaHandler`, `LambdaHandler`, `EventLoopLambdaHandler`, `ByteBufferLambdaHandler`). As noted in the **Current Limitations** section, the ease-of-use varies for each handler protocol and users may not be able to easily determine which protocol best serves their use-case without spending time digging into the library. To reduce this problem and provide users with clear-cut options, we propose replacing all of the existing handler protocols with just two: `LambdaHandler` and `StreamingLambdaHandler`. Both will be explained in the **Detailed Solution** section.
124
124
@@ -262,7 +262,7 @@ public final class LambdaRuntime<Handler>: ServiceLifecycle.Service, Sendable
262
262
}
263
263
```
264
264
265
-
The current API allows for a Lambda function to be tested locally through a mock server by requiring an environment variable named `LOCAL_LAMBDA_SERVER_ENABLED` to be set to `true`. If this environment variable is not set, the program immediately crashes as the user will not have the `AWS_LAMBDA_RUNTIME_API` environment variable on their local machine (set automatically when deployed to AWS Lambda). However, making the user set the `LOCAL_LAMBDA_SERVER_ENABLED` environment variable is an unnecessary step that can be avoided. In the v2 API, the `run()` function will automatically start the mock server when the `AWS_LAMBDA_RUNTIME_API` environment variable cannot be found.
265
+
The current API allows for a Lambda function to be tested locally through a mock server by requiring an environment variable named `LOCAL_LAMBDA_SERVER_ENABLED` to be set to `true`. If this environment variable is not set, the program immediately crashes as the user will not have the `AWS_LAMBDA_RUNTIME_API` environment variable on their local machine (set automatically when deployed to AWS Lambda). However, making the user set the `LOCAL_LAMBDA_SERVER_ENABLED` environment variable is an unnecessary step that can be avoided. In the v2 API, the `run()` function will automatically start the mock server when the `AWS_LAMBDA_RUNTIME_API` environment variable cannot be found.
266
266
267
267
### Lambda
268
268
@@ -283,15 +283,15 @@ Since the library now provides ownership of the `main()` function and allows use
283
283
284
284
To retain support for initialization error reporting, the `Lambda.reportStartupError(any Error)` function gives users the option to manually report initialization errors in their closure handler. Although this should ideally happen implicitly like it currently does in v1, we believe this is a small compromise in comparison to the benefits gained in now being able to cleanly manage the lifecycles of required services in a structured concurrency manner.
285
285
286
-
287
-
>Use-case:
286
+
> Use-case:
287
+
>
288
+
> Assume we want to load a secret for the Lambda function from a secret vault first.
289
+
> If this fails, we want to report the error to the control plane:
288
290
>
289
-
>Assume we want to load a secret for the Lambda function from a secret vault first.
290
-
>If this fails, we want to report the error to the control plane:
291
-
>```swift
292
-
>let secretVault =SecretVault()
291
+
> ```swift
292
+
>let secretVault =SecretVault()
293
293
>
294
-
>do {
294
+
>do {
295
295
>/// !!! Error thrown: secret "foo" does not exist !!!
@@ -305,11 +305,11 @@ To retain support for initialization error reporting, the `Lambda.reportStartupE
305
305
> logger: logger
306
306
> )
307
307
>tryawait serviceGroup.run()
308
-
>} catch {
308
+
>} catch {
309
309
>/// Report startup error straight away to the dedicated initialization error endpoint
310
310
>tryawait Lambda.reportStartupError(error)
311
-
>}
312
-
>```
311
+
>}
312
+
>```
313
313
314
314
### Codable support
315
315
@@ -334,7 +334,7 @@ public protocol LambdaHandler {
334
334
}
335
335
```
336
336
337
-
2. Accepts *any* encoder and decoder object conforming to the `LambdaEventDecoder` and `LambdaOutputEncoder` protocols:
337
+
2. Accepts _any_ encoder and decoder object conforming to the `LambdaEventDecoder` and `LambdaOutputEncoder` protocols:
338
338
339
339
#### LambdaEventDecoder and LambdaOutputEncoder protocols
340
340
@@ -354,13 +354,13 @@ public protocol LambdaOutputEncoder {
354
354
We provide conformances for Foundation’s `JSONDecoder` to `LambdaEventDecoder` and `JSONEncoder` to `LambdaOutputEncoder`.
355
355
356
356
3. Implements its `handle()` method by:
357
-
1. Decoding the `ByteBuffer` event into the generic `Event` type.
358
-
2. Passing the generic `Event` instance to the underlying handler's `handle()` method.
359
-
3. Encoding the generic `Output` returned from the underlying `handle()` into JSON and returning it.
357
+
1. Decoding the `ByteBuffer` event into the generic `Event` type.
358
+
2. Passing the generic `Event` instance to the underlying handler's `handle()` method.
359
+
3. Encoding the generic `Output` returned from the underlying `handle()` into JSON and returning it.
360
360
361
361
#### LambdaCodableAdapter
362
362
363
-
`LambdaCodableAdapter` can implement encoding/decoding for *any* handler conforming to `LambdaHandler` if `Event` is `Decodable` and the `Output` is `Encodable`, meaning that the encoding/decoding stubs do not need to be implemented by the user.
363
+
`LambdaCodableAdapter` can implement encoding/decoding for _any_ handler conforming to `LambdaHandler` if `Event` is `Decodable` and the `Output` is `Encodable`, meaning that the encoding/decoding stubs do not need to be implemented by the user.
364
364
365
365
```swift
366
366
/// Wraps an underlying handler conforming to `CodableLambdaHandler`
0 commit comments