この記事の要点
- Swift サーバーエコシステム向けの新しいオープンソースプロジェクト Swift AWS Lambda Runtime(swift-aws-lambda-runtime)が発表されました。Swift パッケージとして配布され、Amazon Web Services の Lambda プラットフォーム上で動く serverless function を Swift で書けるようにします。
- 低いメモリ使用量、安定した性能、短い起動時間という Swift の特性は、リソース使用量がコストに直結する serverless function のアーキテクチャと相性がよいとされています。
- クロージャを渡すだけの簡潔な API から、ネットワーク処理と同じスレッドでハンドラを実行する性能重視の API まで、段階的に選べる多層 API を備えています。
何が発表されたのか
Swift AWS Lambda Runtime は、AWS Lambda 上で動く serverless function を Swift で実装するためのオープンソースライブラリです。Apple や Amazon を含む Swift コミュニティのエンジニアによる共同開発として進められています。
serverless function は、クラウド上でイベント駆動やアドホックな計算処理を実行する手段として広く使われています。必要なときだけ実行される性質上、スケールやコストを制御しやすい一方で、リソース使用量がそのままコストに影響します。Swift は低いメモリ使用量・安定した性能・短い起動時間を備えるため、この領域に適しているとされています。
このライブラリは AWS Lambda Runtime API の実装であり、AWS のランタイム環境向けにチューニングされた非同期 HTTP クライアントを内蔵しています。
何に使えるのか
クロージャを使う
最も簡単な使い方は、Lambda.run にクロージャを渡すことです。
// モジュールをインポート
import AWSLambdaRuntime
// この例では文字列を受け取り、文字列で応答する
Lambda.run { (context, payload: String, callback) in
callback(.success("Hello, \(payload)"))
}
実際には、ペイロードを Codable でモデル化した JSON にすることが多くなります。JSON のエンコード・デコードは透過的に処理されます。
import AWSLambdaRuntime
// リクエスト。Codable により JSON を透過的にデコードする
private struct Request: Codable {
let name: String
}
// レスポンス。Codable により JSON を透過的にエンコードする
private struct Response: Codable {
let message: String
}
// この例では JSON を受け取り、Codable を使って JSON で応答する
Lambda.run { (context, request: Request, callback) in
callback(.success(Response(message: "Hello, \(request.name)")))
}
Lambda function は SNS、SQS、S3 といった AWS のサービスから発生するイベントによってトリガーされることが多いため、パッケージにはこれらのトリガーイベント型の実装を提供する AWSLambdaEvents モジュールも含まれています。たとえば SQS のメッセージを処理する場合は次のように書けます。
import AWSLambdaRuntime
import AWSLambdaEvents
// この例では SQS のメッセージを受け取り、応答は返さない(Void)
Lambda.run { (context, message: SQS.Message, callback) in
...
callback(.success(Void()))
}
AWSLambdaEvents には、Lambda function を HTTP エンドポイントとして公開する APIGateway との統合のための抽象化も含まれています。
import AWSLambdaRuntime
import AWSLambdaEvents
// この例では APIGateway.V2.Request を受け取り、
// APIGateway.V2.Response で応答する
Lambda.run { (context, request: APIGateway.V2.Request, callback) in
...
callback(.success(APIGateway.V2.Response(statusCode: .accepted)))
}
性能重視の EventLoopLambdaHandler を使う
クロージャとして Lambda function をモデル化する方法は、簡潔であると同時に安全です。Swift AWS Lambda Runtime は、利用者が渡した関数をネットワーク処理スレッドとは別のスレッドにオフロードします。これにより、利用者のコードが遅くなったり応答しなくなったりしても、その裏で Lambda プロセスがランタイムエンジンとやり取りを続けられます。この安全性は、ネットワーク用スレッドと処理用スレッドの間のコンテキストスイッチによるわずかな性能上のコストを伴います。多くの場合、後述の性能重視 API の複雑さよりも、クロージャベース API の簡潔さと安全性のほうが好まれます。
性能が重要な Lambda function では、利用者のコードをネットワークハンドラと同じスレッドで実行する、より複雑な API を選べます。Swift AWS Lambda Runtime は内部のネットワークエンジンに SwiftNIO を使っているため、この API は EventLoop や EventLoopFuture といった SwiftNIO の並行処理プリミティブに基づきます。
import AWSLambdaRuntime
import AWSLambdaEvents
import NIO
// EventLoopLambdaHandler に適合する Lambda ハンドラ
struct Handler: EventLoopLambdaHandler {
typealias In = SNS.Message // リクエスト型
typealias Out = Void // レスポンス型、または Void
// この例では SNS のメッセージを受け取り、応答は返さない(Void)
func handle(context: Lambda.Context, payload: In) -> EventLoopFuture<Out> {
...
context.eventLoop.makeSucceededFuture(Void())
}
}
Lambda.run(Handler())
EventLoopFuture ベースの API は認知的に複雑なだけでなく、扱いに特別な注意が必要です。EventLoopLambdaHandler は利用者の関数をライブラリのネットワークエンジンと同じ EventLoop(スレッド)上で実行するため、その EventLoop を決してブロックしないことが実装側の要件になります。言い換えると、Lambda のコードはブロッキングな API 呼び出しを使ってはいけません。ブロックしてしまうと、ライブラリが Lambda プラットフォームとやり取りできなくなる恐れがあるためです。
導入・今後の位置づけ
これはコミュニティ主導のオープンソースプロジェクトの出発点であり、貢献を積極的に募っているとされています。発表時点でコア API は安定しているとみなされているものの、1.0 に近づく過程で API はまだ変化しうる段階です。さらなる性能チューニング、追加のトリガーイベント、ドキュメントとベストプラクティスの拡充、追加のサンプルなどが、今後の取り組みとして挙げられています。