@@ -25,72 +25,104 @@ struct User: Codable {
2525@main
2626struct LambdaFunction {
2727
28- static func main( ) async throws {
29- try await LambdaFunction ( ) . main ( )
30- }
31-
3228 private let pgClient : PostgresClient
33- private var logger : Logger
29+ private let logger : Logger
30+
3431 private init ( ) throws {
35- self . logger = Logger ( label: " ServiceLifecycleExample " )
32+ var logger = Logger ( label: " ServiceLifecycleExample " )
33+ logger. logLevel = Lambda . env ( " LOG_LEVEL " ) . flatMap ( Logger . Level. init) ?? . info
34+ self . logger = logger
3635
37- self . pgClient = try LambdaFunction . preparePostgresClient (
36+ self . pgClient = try LambdaFunction . createPostgresClient (
3837 host: Lambda . env ( " DB_HOST " ) ?? " localhost " ,
3938 user: Lambda . env ( " DB_USER " ) ?? " postgres " ,
4039 password: Lambda . env ( " DB_PASSWORD " ) ?? " secret " ,
41- dbName: Lambda . env ( " DB_NAME " ) ?? " test " ,
42- logger: logger
40+ dbName: Lambda . env ( " DB_NAME " ) ?? " servicelifecycle " ,
41+ logger: self . logger
4342 )
4443 }
44+
45+ /// Function entry point when the runtime environment is created
4546 private func main( ) async throws {
4647
4748 // Instantiate LambdaRuntime with a handler implementing the business logic of the Lambda function
48- // ok when https://github.com/swift-server/swift-aws-lambda-runtime/pull/523 will be merged
49- //let runtime = LambdaRuntime(logger: logger, body: handler)
50- let runtime = LambdaRuntime ( body: handler)
49+ let runtime = LambdaRuntime ( logger: self . logger, body: self . handler)
5150
5251 /// Use ServiceLifecycle to manage the initialization and termination
5352 /// of the PGClient together with the LambdaRuntime
5453 let serviceGroup = ServiceGroup (
55- services: [ pgClient, runtime] ,
54+ services: [ self . pgClient, runtime] ,
5655 gracefulShutdownSignals: [ . sigterm] ,
5756 cancellationSignals: [ . sigint] ,
58- logger: logger
57+ logger: self . logger
5958 )
59+
60+ // launch the service groups
61+ // this call will return upon termination or cancellation of all the services
6062 try await serviceGroup. run ( )
6163
6264 // perform any cleanup here
63-
6465 }
6566
67+ /// Function handler. This code is called at each function invocation
68+ /// input event is ignored in this demo.
6669 private func handler( event: String , context: LambdaContext ) async -> [ User ] {
6770
68- // input event is ignored here
69-
7071 var result : [ User ] = [ ]
7172 do {
72- // Use initialized service within the handler
73- // IMPORTANT - CURRENTLY WHEN THERE IS AN ERROR, THIS CALL HANGS WHEN DB IS NOT REACHABLE
74- // https://github.com/vapor/postgres-nio/issues/489
75- // this is why there is a timeout, as suggested by
76- // https://github.com/vapor/postgres-nio/issues/489#issuecomment-2186509773
77- logger. info ( " Connecting to the database " )
73+ // IMPORTANT - CURRENTLY, THIS CALL STOPS WHEN DB IS NOT REACHABLE
74+ // See: https://github.com/vapor/postgres-nio/issues/489
75+ // This is why there is a timeout, as suggested Fabian
76+ // See: https://github.com/vapor/postgres-nio/issues/489#issuecomment-2186509773
7877 result = try await timeout ( deadline: . seconds( 3 ) ) {
79- let rows = try await pgClient. query ( " SELECT id, username FROM users " )
80- var users : [ User ] = [ ]
81- for try await (id, username) in rows. decode ( ( Int, String) . self) {
82- logger. info ( " Adding \( id) : \( username) " )
83- users. append ( User ( id: id, username: username) )
84- }
85- return users
78+ // check if table exists
79+ try await prepareDatabase ( )
80+
81+ // query users
82+ return try await self . queryUsers ( )
8683 }
8784 } catch {
88- logger. error ( " PG Error: \( error) " )
85+ logger. error ( " Database Error" , metadata : [ " cause " : " \( String ( reflecting : error) ) " ] )
8986 }
87+
9088 return result
9189 }
9290
93- private static func preparePostgresClient(
91+ /// Prepare the database
92+ /// At first run, this functions checks the database exist and is populated.
93+ /// This is useful for demo purposes. In real life, the database will contain data already.
94+ private func prepareDatabase( ) async throws {
95+ logger. trace ( " Preparing to the database " )
96+ do {
97+ // initial creation of the table. This will fails if it already exists
98+ try await self . pgClient. query ( SQLStatements . createTable)
99+ // it did not fail, it means the table is new and empty
100+ try await self . pgClient. query ( SQLStatements . populateTable)
101+ } catch is PSQLError {
102+ // when there is a database error, it means the table or values already existed
103+ // ignore this error
104+ } catch {
105+ // propagate other errors
106+ throw error
107+ }
108+ }
109+
110+ /// Query the database
111+ private func queryUsers( ) async throws -> [ User ] {
112+ logger. trace ( " Querying to the database " )
113+ var users : [ User ] = [ ]
114+ let query = SQLStatements . queryAllUsers
115+ let rows = try await self . pgClient. query ( query)
116+ for try await (id, username) in rows. decode ( ( Int, String) . self) {
117+ self . logger. trace ( " Adding \( id) : \( username) " )
118+ users. append ( User ( id: id, username: username) )
119+ }
120+ return users
121+ }
122+
123+ /// Create a postgres client
124+ /// ...TODO
125+ private static func createPostgresClient(
94126 host: String ,
95127 user: String ,
96128 password: String ,
@@ -125,6 +157,18 @@ struct LambdaFunction {
125157
126158 return PostgresClient ( configuration: config)
127159 }
160+
161+ private struct SQLStatements {
162+ static let createTable : PostgresQuery =
163+ " CREATE TABLE users (id SERIAL PRIMARY KEY, username VARCHAR(50) NOT NULL); "
164+ static let populateTable : PostgresQuery = " INSERT INTO users (username) VALUES ('alice'), ('bob'), ('charlie'); "
165+ static let queryAllUsers : PostgresQuery = " SELECT id, username FROM users "
166+ }
167+
168+ static func main( ) async throws {
169+ try await LambdaFunction ( ) . main ( )
170+ }
171+
128172}
129173
130174public enum LambdaErrors : Error {
0 commit comments