SERVERLESS IN SWIFT LIKE A BREEZE
ANDREA SCUDERI
SENIOR IOS ENGINEER @ JUST EAT TAKEAWAY
NSLondon
2023.2
AGENDA
▸ Demo App with Serverless
▸ App & Serverless Architecture
▸ Code Structure
▸ Code Generation
▸ Deployment
▸ Why Serverless, Why Swift? Why Breeze?
FULL STACK SWIFT
DEMO APP
GITHUB.COM/SWIFT-SPRINTER/BREEZEDEMOAPP
▸ SwiftUI
▸ Apple Sign In
▸ Serverless API in Swift
▸ CRUD (Create, Read, Update, Delete)
DEMO APP
DATA MODEL
public struct Form: Codable {
public var key: String
public let name: String
public let fields: [Field]
public var createdAt: String?
public var updatedAt: String?
public init(key: String,
name: String,
fields: [Field],
createdAt: String? = nil,
updatedAt: String? = nil) {
self.key = key
self.name = name
self.fields = fields
self.createdAt = createdAt
self.updatedAt = updatedAt
}
enum CodingKeys: String, CodingKey {
case key = "formKey"
case name
case fields
case createdAt
case updatedAt
}
}
public struct Field: Codable {
public let question: String
public let answer: String?
public let choices: [String]?
public let selected: [String]?
public let type: FieldType
public init(question: String,
answer: String? = nil,
choices: [String]? = nil,
selected: [String]? = nil,
type: FieldType) {
self.question = question
self.answer = answer
self.choices = choices
self.selected = selected
self.type = type
}
}
public enum FieldType: String, Codable {
case text
case option
case multiOption
}
SERVERLESS NO-SQL DB
AWS DYNAMODB
{
"formKey": {
"S": "114800A1-162F-4877-81BC-96E9A42E6559"
},
"createdAt": {
"S": "2023-05-06T08:31:37.866Z"
},
"fields": {
"L": [
{
"M": {
"choices": {
"L": [
{
"S": "Cost savings 💰 "
},
{
"S": "Performance ⚡ "
},
{
"S": "Scalability 🚀 "
},
{
"S": "Infrastructure as a Code 💨 "
}
]
},
"question": {
"S": "What is the main benefit of Serverless computing?"
},
"selected": {
"L": [
{
"S": "Cost savings 💰 "
},
{
"S": "Performance ⚡ "
},
{
"S": "Infrastructure as a Code 💨 "
},
{
"S": "Scalability 🚀 "
}
]
},
"type": {
"S": "multiOption"
}
}
},
{
"M": {
MOBILE & SERVERLESS
ARCHITECTURE
Mobile App API Gateway
HTTP/REST
+ (Bearer Token)
OAUTH 2.0
PROVIDER
JWT Token
https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-jwt-authorizer.html
Validation
SERVERLESS ARCHITECTURE
MOBILE & SERVERLESS
CODE ARCHITECTURE
BreezeLambdaAPIClient SharedModel
public struct Form: Codable {
public var key: String
public let name: String
public let fields: [Field]
public var createdAt: String?
public var updatedAt: String?
public init(key: String,
name: String,
fields: [Field],
createdAt: String? = nil,
updatedAt: String? = nil) {
self.key = key
self.name = name
self.fields = fields
self.createdAt = createdAt
self.updatedAt = updatedAt
}
enum CodingKeys: String, CodingKey {
case key = "formKey"
case name
case fields
case createdAt
case updatedAt
}
}
iOS - Swift
…
BreezeLambdaAPI BreezeDynamoDBService
SOTO
SHOW ME THE CODE!
APP API SERVICE
SERVICE-BASED BREEZE LAMBDA API CLIENT
struct FormService: FormServing {
private let apiClient: BreezeLambdaAPIClient<FeedbackForm>
private let session: SessionService
private var token: String? {
session.userSession?.jwtToken
}
init(session: SessionService) {
guard var env = try? APIEnvironment.dev() else {
fatalError("Invalid Environment")
}
env.logger = Logger()
self.session = session
self.apiClient = BreezeLambdaAPIClient<FeedbackForm>(env: env, path: "forms", additionalHeaders: [:])
}
func create(form: FeedbackForm) async throws -> FeedbackForm {
try await apiClient.create(token: token, item: form)
}
func read(key: String) async throws -> FeedbackForm {
try await apiClient.read(token: token, key: key)
}
func update(form: FeedbackForm) async throws -> FeedbackForm {
try await apiClient.update(token: token, item: form)
}
Data Model
Environment API PATH
JWT Token
APP API SERVICE
BREEZE LAMBDA API CLIENT
struct Environment {
static func dev() throws -> APIClientEnv {
try APIClientEnv(session: URLSession.shared, baseURL: "<API GATEWAY BASE URL FROM SERVERLESS DEPLOY>")
}
}
extension FeedbackForm: KeyedCodable {}
struct Logger: APIClientLogging {
func log(request: URLRequest) {
print(request)
}
func log(data: Data, for response: URLResponse) {
print(response)
let value = String(data: data, encoding: .utf8) ?? ""
print(value)
}
}
LAMBDA CODE
ONE LINE OF CODE !!!
import Foundation
import BreezeLambdaAPI
import BreezeDynamoDBService
import SharedModel
extension Form: BreezeCodable { }
BreezeLambdaAPI<Form>.main()
public protocol BreezeCodable: Codable {
var key: String { get set }
var createdAt: String? { get set }
var updatedAt: String? { get set }
}
SWIFT !!!
YES, I CAN UNDERSTAND IT!
INFRASTRUCTURE AS A CODE
SERVERLESS.YML
▸ API Gateway v2
▸ Lambda
▸ DynamoDB
▸ IAM
▸ Package
SERVERLESS.YML ?!?
LOOKS QUITE HARD !
HOW DO I START?
▸ A SMALLER YML CONFIG
▸ COMMAND LINE TOOL GENERATES:
▸ PROJECT FOLDER
▸ SWIFT PACKAGE
▸ SERVERLESS.YML
▸ BUILD SCRIPT
▸ DEPLOYMENT SCRIPT
SERVERLESS WITH BREEZE
CODE GENERATION WITH BREEZE
service: swift-breeze-rest-form-api
awsRegion: us-east-1
swiftVersion: 5.7.3
swiftConfiguration: release
packageName: BreezeFormAPI
buildPath: build
cors: false
authorizer: #optional
name: appleJWT
type: JWTAuthorizer
issuerUrl: https://appleid.apple.com
audience:
- com.andrea.DemoApp #APP BUNDLE ID
breezeLambdaAPI:
targetName: FormAPI
itemCodable: Form
itemKey: formKey
httpAPIPath: /forms
dynamoDBTableNamePrefix: forms
CODE GENERATION
BREEZE COMMAND LINE
swift run breeze -c breeze.yml -t .build/temp
CODE GENERATION
BREEZE COMMAND LINE
CODE BUILD
BREEZE BUILD
./build.sh
❌
build
Docker image: swift:5.7-amazonlinux2
CODE BUILD
BREEZE BUILD
./build.sh
SERVERLESS DEPLOY
BREEZE DEPLOY
./deploy.sh
serverless.yml
build Deployment con
fi
guration
CloudFormation AWS Deployment
serverless deploy
SERVERLESS DEPLOY
BREEZE DEPLOY
./deploy.sh
SERVERLESS LIKE A BREEZE!
BREEZE WORKFLOW
STEP BY STEP WORKFLOW
▸ Git Clone https://github.com/swift-sprinter/Breeze
▸ Copy Breeze.yml con
fi
g from the README
▸ Add the Authorizer to the con
fi
g if you want to secure the API!!!
▸ Adjust the con
fi
guration
▸ Generate the code and customise the data model
▸ Build the Lambdas
▸ Deploy the Serverless
▸ Implement your app using the BreezeLambdaAPIClient
WHY SERVERLESS?
WHY SWIFT?
WHY BREEZE?
WHY SERVERLESS? WHY SWIFT? WHY BREEZE?
▸ Scalability
▸ Reduced Operational Overhead
▸ Pay per use
▸ Faster Time to market
▸ Easy Integration
WHY SERVERLESS?
WHY SERVERLESS? WHY SWIFT? WHY BREEZE?
▸ iOS Developers ❤ it!
▸ No need to learn a new language
▸ Modern and performant language
▸ Safe
▸ Expressive
▸ Fast
WHY SWIFT?
▸ Quick Start!
▸ Project setup
▸ Build
▸ Deploy
▸ Full control
▸ Adaptable
▸ Open Source
WHY SERVERLESS? WHY SWIFT? WHY BREEZE?
WHY BREEZE?
GITHUB BREEZE
GITHUB.COM/SWIFT-SPRINTER/BREEZE
THANK YOU!
ANDREA SCUDERI - SENIOR IOS ENGINEER @ JUST EAT TAKEAWAY
STAY IN TOUCH!
▸ Twitter: @andreascuderi13
▸ Linkedin: https://www.linkedin.com/in/andreascuderi/
▸ Medium: https://medium.com/@andreascuderi73

Serverless in Swift like a Breeze

  • 1.
    SERVERLESS IN SWIFTLIKE A BREEZE ANDREA SCUDERI SENIOR IOS ENGINEER @ JUST EAT TAKEAWAY NSLondon 2023.2
  • 2.
    AGENDA ▸ Demo Appwith Serverless ▸ App & Serverless Architecture ▸ Code Structure ▸ Code Generation ▸ Deployment ▸ Why Serverless, Why Swift? Why Breeze?
  • 3.
  • 4.
    DEMO APP GITHUB.COM/SWIFT-SPRINTER/BREEZEDEMOAPP ▸ SwiftUI ▸Apple Sign In ▸ Serverless API in Swift ▸ CRUD (Create, Read, Update, Delete)
  • 5.
    DEMO APP DATA MODEL publicstruct Form: Codable { public var key: String public let name: String public let fields: [Field] public var createdAt: String? public var updatedAt: String? public init(key: String, name: String, fields: [Field], createdAt: String? = nil, updatedAt: String? = nil) { self.key = key self.name = name self.fields = fields self.createdAt = createdAt self.updatedAt = updatedAt } enum CodingKeys: String, CodingKey { case key = "formKey" case name case fields case createdAt case updatedAt } } public struct Field: Codable { public let question: String public let answer: String? public let choices: [String]? public let selected: [String]? public let type: FieldType public init(question: String, answer: String? = nil, choices: [String]? = nil, selected: [String]? = nil, type: FieldType) { self.question = question self.answer = answer self.choices = choices self.selected = selected self.type = type } } public enum FieldType: String, Codable { case text case option case multiOption }
  • 6.
    SERVERLESS NO-SQL DB AWSDYNAMODB { "formKey": { "S": "114800A1-162F-4877-81BC-96E9A42E6559" }, "createdAt": { "S": "2023-05-06T08:31:37.866Z" }, "fields": { "L": [ { "M": { "choices": { "L": [ { "S": "Cost savings 💰 " }, { "S": "Performance ⚡ " }, { "S": "Scalability 🚀 " }, { "S": "Infrastructure as a Code 💨 " } ] }, "question": { "S": "What is the main benefit of Serverless computing?" }, "selected": { "L": [ { "S": "Cost savings 💰 " }, { "S": "Performance ⚡ " }, { "S": "Infrastructure as a Code 💨 " }, { "S": "Scalability 🚀 " } ] }, "type": { "S": "multiOption" } } }, { "M": {
  • 7.
    MOBILE & SERVERLESS ARCHITECTURE MobileApp API Gateway HTTP/REST + (Bearer Token) OAUTH 2.0 PROVIDER JWT Token https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-jwt-authorizer.html Validation
  • 8.
  • 9.
    MOBILE & SERVERLESS CODEARCHITECTURE BreezeLambdaAPIClient SharedModel public struct Form: Codable { public var key: String public let name: String public let fields: [Field] public var createdAt: String? public var updatedAt: String? public init(key: String, name: String, fields: [Field], createdAt: String? = nil, updatedAt: String? = nil) { self.key = key self.name = name self.fields = fields self.createdAt = createdAt self.updatedAt = updatedAt } enum CodingKeys: String, CodingKey { case key = "formKey" case name case fields case createdAt case updatedAt } } iOS - Swift … BreezeLambdaAPI BreezeDynamoDBService SOTO
  • 10.
  • 11.
    APP API SERVICE SERVICE-BASEDBREEZE LAMBDA API CLIENT struct FormService: FormServing { private let apiClient: BreezeLambdaAPIClient<FeedbackForm> private let session: SessionService private var token: String? { session.userSession?.jwtToken } init(session: SessionService) { guard var env = try? APIEnvironment.dev() else { fatalError("Invalid Environment") } env.logger = Logger() self.session = session self.apiClient = BreezeLambdaAPIClient<FeedbackForm>(env: env, path: "forms", additionalHeaders: [:]) } func create(form: FeedbackForm) async throws -> FeedbackForm { try await apiClient.create(token: token, item: form) } func read(key: String) async throws -> FeedbackForm { try await apiClient.read(token: token, key: key) } func update(form: FeedbackForm) async throws -> FeedbackForm { try await apiClient.update(token: token, item: form) } Data Model Environment API PATH JWT Token
  • 12.
    APP API SERVICE BREEZELAMBDA API CLIENT struct Environment { static func dev() throws -> APIClientEnv { try APIClientEnv(session: URLSession.shared, baseURL: "<API GATEWAY BASE URL FROM SERVERLESS DEPLOY>") } } extension FeedbackForm: KeyedCodable {} struct Logger: APIClientLogging { func log(request: URLRequest) { print(request) } func log(data: Data, for response: URLResponse) { print(response) let value = String(data: data, encoding: .utf8) ?? "" print(value) } }
  • 13.
    LAMBDA CODE ONE LINEOF CODE !!! import Foundation import BreezeLambdaAPI import BreezeDynamoDBService import SharedModel extension Form: BreezeCodable { } BreezeLambdaAPI<Form>.main() public protocol BreezeCodable: Codable { var key: String { get set } var createdAt: String? { get set } var updatedAt: String? { get set } }
  • 14.
    SWIFT !!! YES, ICAN UNDERSTAND IT!
  • 15.
    INFRASTRUCTURE AS ACODE SERVERLESS.YML ▸ API Gateway v2 ▸ Lambda ▸ DynamoDB ▸ IAM ▸ Package
  • 16.
  • 17.
    HOW DO ISTART?
  • 18.
    ▸ A SMALLERYML CONFIG ▸ COMMAND LINE TOOL GENERATES: ▸ PROJECT FOLDER ▸ SWIFT PACKAGE ▸ SERVERLESS.YML ▸ BUILD SCRIPT ▸ DEPLOYMENT SCRIPT SERVERLESS WITH BREEZE CODE GENERATION WITH BREEZE service: swift-breeze-rest-form-api awsRegion: us-east-1 swiftVersion: 5.7.3 swiftConfiguration: release packageName: BreezeFormAPI buildPath: build cors: false authorizer: #optional name: appleJWT type: JWTAuthorizer issuerUrl: https://appleid.apple.com audience: - com.andrea.DemoApp #APP BUNDLE ID breezeLambdaAPI: targetName: FormAPI itemCodable: Form itemKey: formKey httpAPIPath: /forms dynamoDBTableNamePrefix: forms
  • 19.
    CODE GENERATION BREEZE COMMANDLINE swift run breeze -c breeze.yml -t .build/temp
  • 20.
  • 21.
  • 22.
  • 23.
    SERVERLESS DEPLOY BREEZE DEPLOY ./deploy.sh serverless.yml buildDeployment con fi guration CloudFormation AWS Deployment serverless deploy
  • 24.
  • 25.
  • 26.
    BREEZE WORKFLOW STEP BYSTEP WORKFLOW ▸ Git Clone https://github.com/swift-sprinter/Breeze ▸ Copy Breeze.yml con fi g from the README ▸ Add the Authorizer to the con fi g if you want to secure the API!!! ▸ Adjust the con fi guration ▸ Generate the code and customise the data model ▸ Build the Lambdas ▸ Deploy the Serverless ▸ Implement your app using the BreezeLambdaAPIClient
  • 27.
  • 28.
    WHY SERVERLESS? WHYSWIFT? WHY BREEZE? ▸ Scalability ▸ Reduced Operational Overhead ▸ Pay per use ▸ Faster Time to market ▸ Easy Integration WHY SERVERLESS?
  • 29.
    WHY SERVERLESS? WHYSWIFT? WHY BREEZE? ▸ iOS Developers ❤ it! ▸ No need to learn a new language ▸ Modern and performant language ▸ Safe ▸ Expressive ▸ Fast WHY SWIFT?
  • 30.
    ▸ Quick Start! ▸Project setup ▸ Build ▸ Deploy ▸ Full control ▸ Adaptable ▸ Open Source WHY SERVERLESS? WHY SWIFT? WHY BREEZE? WHY BREEZE?
  • 31.
  • 32.
  • 33.
    ANDREA SCUDERI -SENIOR IOS ENGINEER @ JUST EAT TAKEAWAY STAY IN TOUCH! ▸ Twitter: @andreascuderi13 ▸ Linkedin: https://www.linkedin.com/in/andreascuderi/ ▸ Medium: https://medium.com/@andreascuderi73