Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM swift:6.2.0

RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends make git
44 changes: 44 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-outside-of-docker-compose
{
"name": "Docker from Docker Compose",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",

// Use this environment variable if you need to bind mount your local source code into a new container.
"remoteEnv": {
"LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}"
},

"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
"version": "latest",
"enableNonRootDocker": "true",
"moby": "true"
},
"ghcr.io/devcontainers/features/aws-cli:1": {}
},
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"lldb.library": "/usr/lib/liblldb.so"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"swiftlang.swift-vscode"
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "docker --version",

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "vscode"
}
36 changes: 36 additions & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
version: '3'

services:
app:
build:
context: .
dockerfile: Dockerfile

volumes:
# Forwards the local Docker socket to the container.
- /var/run/docker.sock:/var/run/docker-host.sock
# Update this to wherever you want VS Code to mount the folder of your project
- ../..:/workspaces:cached

# Overrides default command so things don't shut down after the process ends.
entrypoint: /usr/local/share/docker-init.sh
depends_on:
- localstack
environment:
- LOCALSTACK_ENDPOINT=http://localstack:4566
- AWS_ACCESS_KEY_ID=test
- AWS_SECRET_ACCESS_KEY=test
- AWS_REGION=us-east-1
command: sleep infinity

# Uncomment the next four lines if you will use a ptrace-based debuggers like C++, Go, and Rust.
cap_add:
- SYS_PTRACE
security_opt:
- seccomp:unconfined

# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)

localstack:
image: localstack/localstack
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot

version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly
3 changes: 3 additions & 0 deletions .sourcekit-lsp/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"$schema": "https://raw.githubusercontent.com/swiftlang/sourcekit-lsp/refs/heads/release/6.1/config.schema.json"
}
1 change: 1 addition & 0 deletions .swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6.2.0
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ localstack:
docker run -it --rm -p "4566:4566" localstack/localstack

local_setup_dynamo_db:
aws --endpoint-url=http://localhost:4566 dynamodb create-table \
aws --endpoint-url=http://localstack:4566 dynamodb create-table \
--table-name Breeze \
--attribute-definitions AttributeName=itemKey,AttributeType=S \
--key-schema AttributeName=itemKey,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
--billing-mode PAY_PER_REQUEST \
--region us-east-1

local_invoke_demo_app:
Expand Down
6 changes: 3 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 6.0
// swift-tools-version: 6.1

import PackageDescription

Expand Down Expand Up @@ -26,8 +26,8 @@ let package = Package(
)
],
dependencies: [
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", from: "2.0.0"),
.package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", from: "0.5.0"),
.package(url: "https://github.com/awslabs/swift-aws-lambda-runtime", from: "2.2.0"),
.package(url: "https://github.com/awslabs/swift-aws-lambda-events.git", from: "0.5.0"),
.package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.0.0"),
.package(url: "https://github.com/soto-project/soto.git", from: "7.0.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.2"),
Expand Down
40 changes: 20 additions & 20 deletions Sources/BreezeDynamoDBService/BreezeDynamoDBService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@ import Logging
/// Defines the interface for a Breeze DynamoDB service.
///
/// Provides methods to access the database manager and to gracefully shutdown the service.
public protocol BreezeDynamoDBServing: Actor {
func dbManager() async -> BreezeDynamoDBManaging
func gracefulShutdown() throws
public protocol BreezeDynamoDBServing: Service {
var dbManager: BreezeDynamoDBManaging { get }
func onGracefulShutdown() async throws
func syncShutdown() throws
}

/// Provides methods to access the DynamoDB database manager and to gracefully shutdown the service.
public actor BreezeDynamoDBService: BreezeDynamoDBServing {
public struct BreezeDynamoDBService: BreezeDynamoDBServing {

private let dbManager: BreezeDynamoDBManaging
public let dbManager: BreezeDynamoDBManaging
private let logger: Logger
private let awsClient: AWSClient
private let httpClient: HTTPClient
private var isShutdown = false

/// Initializes the BreezeDynamoDBService with the provided configuration.
/// - Parameters:
Expand All @@ -46,11 +46,14 @@ public actor BreezeDynamoDBService: BreezeDynamoDBServing {
httpConfig: BreezeHTTPClientConfig,
logger: Logger,
DBManagingType: BreezeDynamoDBManaging.Type = BreezeDynamoDBManager.self
) async {
) {
logger.info("Init DynamoDBService with config...")
logger.info("region: \(config.region)")
logger.info("tableName: \(config.tableName)")
logger.info("keyName: \(config.keyName)")
if config.endpoint != nil {
logger.info("endpoint: \(config.endpoint!)")
}
self.logger = logger

let timeout = HTTPClient.Configuration.Timeout(
Expand All @@ -76,10 +79,9 @@ public actor BreezeDynamoDBService: BreezeDynamoDBServing {
logger.info("DBManager is ready.")
}

/// Returns the BreezeDynamoDBManaging instance.
public func dbManager() async -> BreezeDynamoDBManaging {
logger.info("Starting DynamoDBService...")
return self.dbManager
public func run() async throws {
try await gracefulShutdown()
try await onGracefulShutdown()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you shutting down immediately at run() ? Does it work ? Why ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function try await gracefulShutdown() waits until graceful shutdown is triggered as documented in ServiceLifecycle

https://swiftpackageindex.com/swift-server/swift-service-lifecycle/main/documentation/servicelifecycle/gracefulshutdown()

}

/// Gracefully shutdown the service and its components.
Expand All @@ -89,21 +91,19 @@ public actor BreezeDynamoDBService: BreezeDynamoDBServing {
/// It also logs the shutdown process.
/// This method is idempotent;
/// - Important: This method must be called at leat once to ensure that resources are released properly. If the method is not called, it will lead to a crash.
public func gracefulShutdown() throws {
guard !isShutdown else { return }
isShutdown = true
public func onGracefulShutdown() async throws {
logger.info("Stopping DynamoDBService...")
try awsClient.syncShutdown()
try await awsClient.shutdown()
logger.info("DynamoDBService is stopped.")
logger.info("Stopping HTTPClient...")
try httpClient.syncShutdown()
try await httpClient.shutdown()
logger.info("HTTPClient is stopped.")
}

deinit {
guard !isShutdown else { return }
try? awsClient.syncShutdown()
try? httpClient.syncShutdown()
/// Sync shutdown
public func syncShutdown() throws {
try awsClient.syncShutdown()
try httpClient.syncShutdown()
}
}

4 changes: 2 additions & 2 deletions Sources/BreezeLambdaAPI/BreezeAPIConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,10 @@ public struct BreezeAPIConfiguration: APIConfiguring {
/// This method is used to retrieve the name of the primary key in the DynamoDB table that will be used by the Breeze Lambda API.
/// - Important: The key name is essential for identifying items in the DynamoDB table.
func keyName() throws -> String {
guard let tableName = Lambda.env("DYNAMO_DB_KEY") else {
guard let keyName = Lambda.env("DYNAMO_DB_KEY") else {
throw BreezeLambdaAPIError.keyNameNotFound
}
return tableName
return keyName
}

/// Returns the endpoint for the Breeze Lambda API.
Expand Down
24 changes: 15 additions & 9 deletions Sources/BreezeLambdaAPI/BreezeLambdaAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public actor BreezeLambdaAPI<T: BreezeCodable>: Service {
let timeout: TimeAmount
private let serviceGroup: ServiceGroup
private let apiConfig: any APIConfiguring
private let dynamoDBService: BreezeDynamoDBService

/// Initializes the BreezeLambdaAPI with the provided API configuration.
/// - Parameter apiConfig: An object conforming to `APIConfiguring` that provides the necessary configuration for the Breeze API.
Expand All @@ -63,19 +64,18 @@ public actor BreezeLambdaAPI<T: BreezeCodable>: Service {
logger: logger
)
let operation = try apiConfig.operation()
let dynamoDBService = await BreezeDynamoDBService(
self.dynamoDBService = BreezeDynamoDBService(
config: config,
httpConfig: httpConfig,
logger: logger
)
let breezeLambdaService = BreezeLambdaService<T>(
dynamoDBService: dynamoDBService,
operation: operation,
logger: logger
)
let dbManager = dynamoDBService.dbManager
let breezeApi = BreezeLambdaHandler<T>(dbManager: dbManager, operation: operation)
let runtime = LambdaRuntime(body: breezeApi.handle)
self.serviceGroup = ServiceGroup(
services: [breezeLambdaService],
gracefulShutdownSignals: [.sigterm, .sigint],
services: [runtime, dynamoDBService],
gracefulShutdownSignals: [.sigint],
cancellationSignals: [.sigterm],
logger: logger
)
} catch {
Expand All @@ -90,7 +90,13 @@ public actor BreezeLambdaAPI<T: BreezeCodable>: Service {
/// The internal ServiceGroup will handle the lifecycle of the BreezeLambdaAPI, including starting and stopping the service gracefully.
public func run() async throws {
logger.info("Starting BreezeLambdaAPI...")
try await serviceGroup.run()
do {
try await serviceGroup.run()
} catch {
try dynamoDBService.syncShutdown()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the life cycle group must do that. You should not shutdown() or cancel() a service part of the group

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, it does unless there is a failure condition. Without this BreezeLambdaAPITests will crash.
I'll check if there is a better way.

logger.error("BreezeLambdaAPI failed with error: \(error.localizedDescription)")
throw error
}
logger.info("BreezeLambdaAPI is stopped successfully")
}
}
Loading