Swift offers a compelling opportunity to build reliable, scalable and lightweight micro-services. This talk will discuss how I am investigating the use of Swift micro-services - running in ECS and potentially Lambda, fronted by API gateway - with AWS technologies such as managing data models with DynamoDb.
2. ABOUT ME
• 4 years helping architect, build and
maintain frameworks for Amazon.com
• Recently moved to CoreTechnology in
AmazonVideo
• Grew up in Melbourne,Australia
4. WHY IS JAVA PREVALENT?
• Not significant learning curve
• Harder to make mistakes than C++
• Enables higher developer velocity to create
5. 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).
6. 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)")
}
7. 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
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 datasets or fast concurrent access more suited to NOSQL
databases
• Database schema based around data access patterns to enable efficient
queries
10. • 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
15. 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
16. 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
}
}
17. • 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
}
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 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()