SWIFT MICRO-SERVICES
AND AWSTECHNOLOGIES
Simon Pilkington
Senior Software Engineer
CoreTechnology
AmazonVideo
ABOUT ME
• 4 years helping architect, build and
maintain frameworks for Amazon.com
• Recently moved to CoreTechnology in
AmazonVideo
• Grew up in Melbourne,Australia
WHY SWIFT?
WHY IS JAVA PREVALENT?
• Not significant learning curve
• Harder to make mistakes than C++
• Enables higher developer velocity to create
WHY LOOK BEYOND JAVA?
Build Logic What's included
This package includes tools to help with:
• Code Coverage (JaCoco, PIT)
• Code Style (Checkstyle and matching IntelliJ formatter)
• Code Quality (FindBugs, PMD, Copy Paste Detector, JDepend).
CODE GENERATION FOR SWIFT SERVICE
• Code Generation of model objects, operation stubs and unit tests
• Use Codable conformance for serialization and de-serialization
• Swagger document can also be passed to Cloud Formation to create an API Gateway to
front the service.
Generated Code /**
Handler for the HelloWorld operation.
- Parameters:
- input: The validated HelloWorldRequest object being passed to this operation.
- context: The activities context provided for this operation.
- Returns: The HelloWorldResponse object to be passed back from the caller of this operation.
Will be validated before being returned to caller.
*/
func handleHelloWorld(input: HelloWorldModel.HellowWorldRequest,
context: HellowWorldActivitiesContext) -> HelloWorldModel.HellowWorldResponse {
return HelloWorldModel.HelloWorldResponse(greeting: "Hello (input.name)")
}
RUNTIME
• Initial proof of concept as a container running on ECS
• Using an implementation of IBM’s LoggerAPI to write logs to Cloudwatch
• Retrieve AWS credentials from the container with automatic credential
rotation
• Manages health checks, unresponsive containers are automatically
replaced
DATABASE SERIALIZATION
Code
Code
struct Fruit: Codable {
let fruitID: FruitID
let accessRole: AccessRole
let sweet: Int
let sour: Int
}
struct Location: Codable {
let locationID: LocationID
let fruitID: FruitID
let locationPath: String
let locationType: LocationType
let checksum: Int
let checksumType: ChecksumType
}
• Large datasets or fast concurrent access more suited to NOSQL
databases
• Database schema based around data access patterns to enable efficient
queries
• DynamoDb’s DynamoDBMapper in the Java AWS SDK
implements optimistic locking by managing an invisible row
version attribute
• Currently there is a pull request to implement polymorphic
tables similarly using inheritance and managing an invisible
subclass attribute
But Inheritance…
PROTOCOLSTOTHE RESCUE!
Probably look familiar
Swift Language public protocol Encodable {
public func encode(to encoder: Encoder) throws
}
public protocol Decodable {
public init(from decoder: Decoder) throws
}
public typealias Codable = Decodable & Encodable
DYNAMO SPECIFIC ENCODING
JSON {
"Name": {"S": "Citrus"}
"Description": {"S": "They’re alright."}
}
DynamoDb
Codable
Object
Codable
Object
DynamoEncoder JSONEncoder
DynamoDecoder JSONDecoder
OPERATION IMPLEMENTATION
Code func handleRegisterFamily(input: FruitModel.FamilyAttributes,
context: FruitActivitiesContext)
throws -> FruitModel.FamilyIdentity {
let currentID = context.idGenerator()
let partitionKey = familyKeyPrefix + currentID
let key = DefaultIdentityCompositePrimaryKey(partitionKey: partitionKey,
sortKey: partitionKey)
let newDatabaseRow = TypedDatabaseItem.newItem(withKey: key, andValue: input)
try context.dynamoClient.putItem(newDatabaseRow)
return FruitModel.FamilyIdentity(familyID: partitionKey)
}
TYPEDDATABASEITEM
• Handles row type, versioning, create and last modified timestamps.
• Accepts a row value conforming to Codable
• By default a newTypedDatabaseItem will fail to overwrite an existing
row
• CompositePrimaryKey defines row identity - partition and sort keys;
generic in a protocol that defines the key attribute names
Library Code
public struct TypedDatabaseItem<RowIdentity : DynamoRowIdentity, RowType : Codable>: Codable {
...
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let storedRowTypeName = try values.decode(String.self, forKey: .rowType)
self.createDate = try values.decode(Date.self, forKey: .createDate)
// get the type that is being requested to be decoded into
let requestedRowTypeName = getTypeRowIdentity(type: RowType.self)
// if the stored rowType is not what we should attempt to decode into
guard storedRowTypeName == requestedRowTypeName else {
// throw an error to avoid accidentally decoding into the incorrect type
throw SwiftDynamoError.typeMismatch(expected: storedRowTypeName,
provided: requestedRowTypeName)
}
self.compositePrimaryKey = try CompositePrimaryKey(from: decoder)
self.rowStatus = try RowStatus(from: decoder)
self.rowValue = try RowType(from: decoder)
self.canOverwriteExistingRow = false
self.onlyOverwriteVersionNumber = nil
}
}
• createUpdatedItem function creates a new instance with updated last
modified timestamp and incremented row version.
• By default will fail to overwrite a row version other than the version
it was created from
Code let updatedFruitRow =
fruitDatabaseItem.createUpdatedItem(withValue: updatedFruitAttributes)
do {
try context.dynamoClient.putItem(updatedAccountRow)
} catch SwiftDynamoError.conditionalCheckFailed(_) {
// handle the error
}
BUT WHAT ABOUT QUERIES?
AGAIN PROTOCOLSTOTHE RESCUE
• Similar toTypedDatabaseItem but returns a row type of Codable, de-
serialized according to the type specified in the data row
Library Code public protocol PossibleItemTypes {
static var types: [Codable.Type] { get }
}
public struct PolymorphicDatabaseItem<RowIdentity : DynamoRowIdentity,
PossibleTypes : PossibleItemTypes> : Decodable {
...
}
A FINAL ODETO PROTOCOLS
Generated Code public protocol LocationShape {
associatedtype LocationTypeType : CustomStringConvertible
associatedtype ChecksumTypeType : CustomStringConvertible
var locationPath: String { get }
var locationType: LocationTypeType { get }
var checksum: String { get }
var checksumType: ChecksumTypeType { get }
func asFruitModelLocation() throws -> Location
}
Code extension SaladModel.Location : FruitModel.LocationShape {}
...
let location = try input.location.asFruitModelLocation()

Swift Micro-services and AWS Technologies

  • 1.
    SWIFT MICRO-SERVICES AND AWSTECHNOLOGIES SimonPilkington Senior Software Engineer CoreTechnology AmazonVideo
  • 2.
    ABOUT ME • 4years helping architect, build and maintain frameworks for Amazon.com • Recently moved to CoreTechnology in AmazonVideo • Grew up in Melbourne,Australia
  • 3.
  • 4.
    WHY IS JAVAPREVALENT? • Not significant learning curve • Harder to make mistakes than C++ • Enables higher developer velocity to create
  • 5.
    WHY LOOK BEYONDJAVA? Build Logic What's included This package includes tools to help with: • Code Coverage (JaCoco, PIT) • Code Style (Checkstyle and matching IntelliJ formatter) • Code Quality (FindBugs, PMD, Copy Paste Detector, JDepend).
  • 6.
    CODE GENERATION FORSWIFT SERVICE • Code Generation of model objects, operation stubs and unit tests • Use Codable conformance for serialization and de-serialization • Swagger document can also be passed to Cloud Formation to create an API Gateway to front the service. Generated Code /** Handler for the HelloWorld operation. - Parameters: - input: The validated HelloWorldRequest object being passed to this operation. - context: The activities context provided for this operation. - Returns: The HelloWorldResponse object to be passed back from the caller of this operation. Will be validated before being returned to caller. */ func handleHelloWorld(input: HelloWorldModel.HellowWorldRequest, context: HellowWorldActivitiesContext) -> HelloWorldModel.HellowWorldResponse { return HelloWorldModel.HelloWorldResponse(greeting: "Hello (input.name)") }
  • 7.
    RUNTIME • Initial proofof concept as a container running on ECS • Using an implementation of IBM’s LoggerAPI to write logs to Cloudwatch • Retrieve AWS credentials from the container with automatic credential rotation • Manages health checks, unresponsive containers are automatically replaced
  • 8.
    DATABASE SERIALIZATION Code Code struct Fruit:Codable { let fruitID: FruitID let accessRole: AccessRole let sweet: Int let sour: Int } struct Location: Codable { let locationID: LocationID let fruitID: FruitID let locationPath: String let locationType: LocationType let checksum: Int let checksumType: ChecksumType }
  • 9.
    • Large datasetsor fast concurrent access more suited to NOSQL databases • Database schema based around data access patterns to enable efficient queries
  • 10.
    • DynamoDb’s DynamoDBMapperin the Java AWS SDK implements optimistic locking by managing an invisible row version attribute • Currently there is a pull request to implement polymorphic tables similarly using inheritance and managing an invisible subclass attribute
  • 11.
  • 12.
    PROTOCOLSTOTHE RESCUE! Probably lookfamiliar Swift Language public protocol Encodable { public func encode(to encoder: Encoder) throws } public protocol Decodable { public init(from decoder: Decoder) throws } public typealias Codable = Decodable & Encodable
  • 13.
    DYNAMO SPECIFIC ENCODING JSON{ "Name": {"S": "Citrus"} "Description": {"S": "They’re alright."} } DynamoDb Codable Object Codable Object DynamoEncoder JSONEncoder DynamoDecoder JSONDecoder
  • 14.
    OPERATION IMPLEMENTATION Code funchandleRegisterFamily(input: FruitModel.FamilyAttributes, context: FruitActivitiesContext) throws -> FruitModel.FamilyIdentity { let currentID = context.idGenerator() let partitionKey = familyKeyPrefix + currentID let key = DefaultIdentityCompositePrimaryKey(partitionKey: partitionKey, sortKey: partitionKey) let newDatabaseRow = TypedDatabaseItem.newItem(withKey: key, andValue: input) try context.dynamoClient.putItem(newDatabaseRow) return FruitModel.FamilyIdentity(familyID: partitionKey) }
  • 15.
    TYPEDDATABASEITEM • Handles rowtype, versioning, create and last modified timestamps. • Accepts a row value conforming to Codable • By default a newTypedDatabaseItem will fail to overwrite an existing row • CompositePrimaryKey defines row identity - partition and sort keys; generic in a protocol that defines the key attribute names
  • 16.
    Library Code public structTypedDatabaseItem<RowIdentity : DynamoRowIdentity, RowType : Codable>: Codable { ... public init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) let storedRowTypeName = try values.decode(String.self, forKey: .rowType) self.createDate = try values.decode(Date.self, forKey: .createDate) // get the type that is being requested to be decoded into let requestedRowTypeName = getTypeRowIdentity(type: RowType.self) // if the stored rowType is not what we should attempt to decode into guard storedRowTypeName == requestedRowTypeName else { // throw an error to avoid accidentally decoding into the incorrect type throw SwiftDynamoError.typeMismatch(expected: storedRowTypeName, provided: requestedRowTypeName) } self.compositePrimaryKey = try CompositePrimaryKey(from: decoder) self.rowStatus = try RowStatus(from: decoder) self.rowValue = try RowType(from: decoder) self.canOverwriteExistingRow = false self.onlyOverwriteVersionNumber = nil } }
  • 17.
    • createUpdatedItem functioncreates a new instance with updated last modified timestamp and incremented row version. • By default will fail to overwrite a row version other than the version it was created from Code let updatedFruitRow = fruitDatabaseItem.createUpdatedItem(withValue: updatedFruitAttributes) do { try context.dynamoClient.putItem(updatedAccountRow) } catch SwiftDynamoError.conditionalCheckFailed(_) { // handle the error }
  • 18.
  • 19.
    AGAIN PROTOCOLSTOTHE RESCUE •Similar toTypedDatabaseItem but returns a row type of Codable, de- serialized according to the type specified in the data row Library Code public protocol PossibleItemTypes { static var types: [Codable.Type] { get } } public struct PolymorphicDatabaseItem<RowIdentity : DynamoRowIdentity, PossibleTypes : PossibleItemTypes> : Decodable { ... }
  • 20.
    A FINAL ODETOPROTOCOLS Generated Code public protocol LocationShape { associatedtype LocationTypeType : CustomStringConvertible associatedtype ChecksumTypeType : CustomStringConvertible var locationPath: String { get } var locationType: LocationTypeType { get } var checksum: String { get } var checksumType: ChecksumTypeType { get } func asFruitModelLocation() throws -> Location } Code extension SaladModel.Location : FruitModel.LocationShape {} ... let location = try input.location.asFruitModelLocation()