The breeze
package subcommand generate-lambda-api
provides the code to build a Serverless REST API in Swift based on AWS Lambda, APIGateway and DynamoDB.
The following diagram represents the infrastructure architecture of a CRUD REST Serverless API:
The APIGateway exposes the API interface through endpoints and converts the HTTP requests to APIGatewayV2Request events for the Lambdas. Each Lambda receives events from the APIGateway, decodes the events to extract parameters, operates on a DynamoDB table and returns a response payload to the APIGateway. DynamoDB will be accessed through the Lambdas to persist a key-value pair representing data.
With a single line of code, Breeze implements all the Lambdas required for the CRUD interface converting APIGatewayV2Request to an operation on a DynamoDB table and responding with APIGatewayV2Response to the APIGateway.
Define a Codable
struct or class like the Item
one's in the example and pass it to BreezeLambdaAPI
using the type placeholder.
import Foundation
import BreezeLambdaAPI
import BreezeDynamoDBService
struct Item: Codable {
public var key: String
public let name: String
public let description: String
public var createdAt: String?
public var updatedAt: String?
enum CodingKeys: String, CodingKey {
case key
case name
case description
case createdAt
case updatedAt
}
}
extension Item: BreezeCodable { }
BreezeLambdaAPI<Item>.main()
It's required the Codable
struct or class to conform to the BreezeCodable
protocol:
public protocol BreezeCodable: Codable {
var key: String { get set }
var createdAt: String? { get set }
var updatedAt: String? { get set }
}
The code above is the business logic required inside all the Lambdas.
All you need to do is to decide the struct conforming BreezeCodable
to persist on DynamoDB.
Each lambda will be initialized with a specific _HANDLER
and it will run the code to implement the required logic needed by one of the CRUD functions. The code needs to be packaged and deployed using the referenced architecture.
Optimistic locking is a strategy to ensure that the BreezeCodable Item is not updated by another request before updating or deleting it.
The fields updatedAt
and createdAt
are used to implement optimistic locking.
Refer to the DynamoDB documentation for more details.
To package the Lambda is required to create a Swift Package using the following Package.swift
file.
// swift-tools-version:5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "swift-breeze-item-api",
platforms: [
.macOS(.v13),
],
products: [
.executable(name: "ItemAPI", targets: ["ItemAPI"]),
],
dependencies: [
.package(url: "https://github.com/swift-serverless/Breeze.git", from: "0.2.0"),
],
targets: [
.executableTarget(
name: "ItemAPI",
dependencies: [
.product(name: "BreezeLambdaAPI", package: "Breeze"),
.product(name: "BreezeDynamoDBService", package: "Breeze"),
]
)
]
)
To be executed on a Lambda, the package needs to be built on AmazonLinux2
and deployed.
The API can be deployed on AWS in multiple ways.
Refer to the Examples/ItemAPI folder to explore a deployment example using the Serverless Framework.
The package contains a command line tool to generate the deployment of the swift package, the serverless.yml
file and the relevant commands to deploy the Lambda code on AWS using the Serverless Framework.
swift run breeze generate-lambda-api --help
output:
USAGE: breeze generate-lambda-api --config-file <config-file> --target-path <target-path> [--force-overwrite] [-y]
OPTIONS:
-c, --config-file <config-file>
YML configurarion file
-t, --target-path <target-path>
Target path
-f, --force-overwrite Force target path overwrite
-y
-h, --help Show help information.
Define a configuration file with the following format:
service: swift-breeze-rest-item-api
awsRegion: us-east-1
swiftVersion: 5.10.1
swiftConfiguration: release
packageName: BreezeItemAPI
buildPath: build
cors: false
authorizer: #optional
name: appleJWT
type: JWTAuthorizer
issuerUrl: https://appleid.apple.com
audience:
- APP_BUNDLE_IDENTIFIER #Change this with the App BUNDLE_IDENTIFIER
breezeLambdaAPI:
targetName: ItemAPI
itemCodable: Item
itemKey: itemKey
httpAPIPath: /items
dynamoDBTableNamePrefix: items
Configuration parameters:
awsRegion
: AWS RegionswiftVersion
: Swift versionswiftConfiguration
: Swift configuration (debug or release)packageName
: Swift Package namebuildPath
: Swift Package build path where the Lambda executable will be generatedcors
: Enable CORS (default: false)authorizer
: Optional. If defined, the API will be protected by the specified authorizer. The authorizer can be a custom one or a predefined one.APP_BUNDLE_IDENTIFIER
is the App Bundle Identifier of the iOS App that will use the API. If you don't want to use a custom authorizer, remove theauthorizer
section.breezeLambdaAPI
: Breeze Lambda API configurationtargetName
: The name of the target that will be generated by the Swift Package ManageritemCodable
: The name of theCodable
struct or class that will be persisted on DynamoDBitemKey
: The name of the key of theCodable
struct or class that will be persisted on DynamoDBhttpAPIPath
: The path of the APIdynamoDBTableNamePrefix
: The prefix of the DynamoDB table name
The following command will run using the example configuration file and generate the deployment files in the .build/temp
folder.
swift run breeze -c Sources/BreezeCommand/Resources/breeze.yml -t .build/temp
output:
⚙️ Loading configuration file
/Users/andreascuderi/Documents/workspace/Breeze/Sources/BreezeCommand/Resources/breeze.yml
service: swift-breeze-rest-item-api
awsRegion: us-east-1
swiftVersion: 5.10.1
swiftConfiguration: release
packageName: BreezeItemAPI
buildPath: build
cors: false
breezeLambdaAPI:
targetName: ItemAPI
itemCodable: Item
itemKey: itemKey
httpAPIPath: /items
dynamoDBTableNamePrefix: items
🔎 Verifing target path
🧹 .build/temp
✅ Target path ready!
📁 Generating project from template
📄 /Users/andreascuderi/Documents/workspace/Breeze/.build/temp/SwiftPackage/Package.swift
📄 /Users/andreascuderi/Documents/workspace/Breeze/.build/temp/SwiftPackage/Sources/SwiftTarget/main.swift
📄 /Users/andreascuderi/Documents/workspace/Breeze/.build/temp/Dockerfile
📄 /Users/andreascuderi/Documents/workspace/Breeze/.build/temp/remove.sh
📄 /Users/andreascuderi/Documents/workspace/Breeze/.build/temp/.gitignore
📄 /Users/andreascuderi/Documents/workspace/Breeze/.build/temp/deploy.sh
📄 /Users/andreascuderi/Documents/workspace/Breeze/.build/temp/update.sh
📄 /Users/andreascuderi/Documents/workspace/Breeze/.build/temp/Makefile
📄 /Users/andreascuderi/Documents/workspace/Breeze/.build/temp/README.md
📄 /Users/andreascuderi/Documents/workspace/Breeze/.build/temp/build.sh
📄 /Users/andreascuderi/Documents/workspace/Breeze/.build/temp/swagger.json
🛫 .build/temp/SwiftPackage/Sources/SwiftTarget
🛬 .build/temp/SwiftPackage/Sources/ItemAPI
🛫 .build/temp/SwiftPackage
🛬 .build/temp/BreezeItemAPI
📄 /Users/andreascuderi/Documents/workspace/Breeze/.build/temp/serverless.yml
📄 /Users/andreascuderi/Documents/workspace/Breeze/.build/temp/serverless-x86_64.yml
✅ Project is ready at target-path
.build/temp
🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵
🎵💨💨💨💨💨💨🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵
🎵💨🎵🎵🎵🎵🎵💨🎵💨💨💨💨💨🎵🎵💨💨💨💨💨💨🎵💨💨💨💨💨💨🎵💨💨💨💨💨💨🎵💨💨💨💨💨💨🎵
🎵💨🎵🎵🎵🎵🎵💨🎵💨🎵🎵🎵🎵💨🎵💨🎵🎵🎵🎵🎵🎵💨🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵💨🎵🎵💨🎵🎵🎵🎵🎵🎵
🎵💨💨💨💨💨💨🎵🎵💨🎵🎵🎵🎵💨🎵💨💨💨💨💨🎵🎵💨💨💨💨💨🎵🎵🎵🎵🎵💨🎵🎵🎵💨💨💨💨💨🎵🎵
🎵💨🎵🎵🎵🎵🎵💨🎵💨💨💨💨💨🎵🎵💨🎵🎵🎵🎵🎵🎵💨🎵🎵🎵🎵🎵🎵🎵🎵💨🎵🎵🎵🎵💨🎵🎵🎵🎵🎵🎵
🎵💨🎵🎵🎵🎵🎵💨🎵💨🎵🎵🎵💨🎵🎵💨🎵🎵🎵🎵🎵🎵💨🎵🎵🎵🎵🎵🎵🎵💨🎵🎵🎵🎵🎵💨🎵🎵🎵🎵🎵🎵
🎵💨💨💨💨💨💨🎵🎵💨🎵🎵🎵🎵💨🎵💨💨💨💨💨💨🎵💨💨💨💨💨💨🎵💨💨💨💨💨💨🎵💨💨💨💨💨💨🎵
🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵
💨 Use the following commands to build & deploy
cd .build/temp
./build.sh
./deploy.sh
Follow the instructions to build and deploy the project. For more information about the deployment, please refer to the generated README.md
file.
During the Lambda's initialization, the BreezeLambdaAPI
reads the configuration from the following Environment
variables:
AWS_REGION
: AWS Region_HANDLER
: The handler name specifies the CRUD operation implemented by the Lambda using the following format{executable_name}.{BreezeOperation}
or{BreezeOperation}
enum BreezeOperation: String {
case create
case read
case update
case delete
case list
}
(example: build/RestAPI.create
where build/RestAPI
is the executable name and create
is the BreezeOperation).
DYNAMO_DB_TABLE_NAME
: DynamoDB table name.DYNAMO_DB_KEY
: DynamoDB Primary Key
BreezeLambdaAPI
receives an APIGateway event, extracts the relevant parameters and performs a BreezeOperation
on BreezeDynamoDBService
.
create
Decodes a BreezeCodable
from the APIGatewayV2Request.body
and calls createItem
on BreezeDynamoDBService
.
Returns the created BreezeCodable
.
read
Gets the value of the BreezeCodable.key
from the APIGatewayV2Request.pathParameters
dictionary and calls readItem
on BreezeDynamoDBService
.
Returns the BreezeCodable
if persisted on DynamoDB.
update
Decodes a BreezeCodable
from the APIGatewayV2Request.body
and calls updateItem
on BreezeDynamoDBService
.
Returns the updated BreezeCodable
.
delete
Gets the value of the BreezeCodable.key
from the APIGatewayV2Request.pathParameters
dictionary, the value of updatedAt
and createdAt
from APIGatewayV2Request.queryStringParameters
dictionary and calls deleteItem
on BreezeDynamoDBService
.
Returns the BreezeCodable
if persisted on DynamoDB.
list
Gets the value of the exclusiveStartKey
and limit
from the APIGatewayV2Request.queryStringParameters
dictionary and calls listItems
on BreezeDynamoDBService
.
Returns the ListResponse
containing the items if persisted on DynamoDB.
struct ListResponse<T: Codable>: Codable {
let items: [T]
let lastEvaluatedKey: String?
}
(See SotoDynamoDB documentation for more info *)