Using AWS DynamoDB from a Command Line Swift Application
Writing command line applications in Swift is fun. What if it was possible to persist data in the cloud from the command line, with just a few lines of code? Turns out, it is.
In this article we will learn how to write and read data from AWS DynamoDB from a command line application in Swift.
Prerequisites
- Xcode 11 or Xcode 12
- AWS account (create one here)
Installing AWS CLI
You can skip this step if you already have the AWS CLI tool installed and configured.
While not required, having this CLI tool is very handy.
$ brew install awscli
Once installed, proceed to configure it with your credentials, selecting an AWS region to work with (I recommend “us-east-2” or “us-west-2”).
$ aws configure
Configuring the Swift Package
Let’s begin our journey by creating a new command line application.
$ mkdir dynamodb-demo && cd dynamodb-demo
$ swift package init --type executable
$ swift build
Hello world!
Adding Soto for Accessing AWS Services
We will add Soto to our Package.swift
, the most comprehensive Swift AWS SDK.
$ vim Package.swift
Update both package dependencies and target dependencies to add Soto.
// swift-tools-version:5.3
import PackageDescriptionlet package = Package(
name: "dynamodb-demo",
dependencies: [
.package(url: "https://github.com/soto-project/soto",
from: "5.0.0-beta.2")
],
targets: [
.target(name: "dynamodb-demo", dependencies: [
.product(name: "SotoDynamoDB", package: "soto"),
]),
.testTarget(name: "dynamodb-demoTests",
dependencies: ["dynamodb-demo"]),
]
)
Build and run the package to ensure everything works.
$ swift run
Hello world!
Creating a DynamoDB Table
While we could create a table using the web console, we are going to use the AWS command line tool instead. Note that we could also use Soto to create the table from Swift.
$ aws dynamodb list-tables
{
"TableNames": []
}
Let’s create a table “swift-demo”. Best practices recommend using a combination of a Partition Key (PK) and Sort Key (SK). For this demo, we are using a simple partition key:
$ aws dynamodb create-table --table-name swift-demo --key-schema AttributeName=PK,KeyType=HASH --attribute-definitions AttributeName=PK,AttributeType=S --billing-mode PAY_PER_REQUEST
At this point, we should see our table when using list-tables
.
$ aws dynamodb list-tables
{
"TableNames": [
"swift-demo"
]
}
Writing data to DynamoDB
We are ready to write some Swift code, this is what we are here for, right?
For this demo, we are going to write a simple key-value store. We want to define two commands, that will be used as follows:
$ swift run dynamodb-demo set foo bar
foo=bar$ swift run dynamodb-demo get foo
bar
The set
command above will take two parameters, the key (“foo”) and the value (“bar”). Let’s update main.swift
to read those arguments.
For now, we add an empty implementation for both get
and set
commands. If the arguments passed in do not match the expected format, the application will output a brief usage description.
import Foundationlet arguments = ProcessInfo.processInfo.argumentsif arguments.count == 4, arguments[1] == "set" {
let key = arguments[2]
let value = arguments[3]
print("\(key)=\(value)")
}
else if arguments.count == 3, arguments[1] == "get" {
let key = arguments[2]
let value = ""
print(value)
}
else {
print("""
Usage:
get <key> retrieve a value
set <key> <value> store a value
""")
}
For more advanced command line argument parsing, it is recommended to use the Swift Argument Parser package.
Next, we add a method for writing key-values to DynamoDB.
if arguments.count == 4, arguments[1] == "set" {
let key = arguments[2]
let value = arguments[3]
try write(key: key, value: value) // <--- new method
print("\(key)=\(value)")
}
This method will throw an exception if there is an error, like a network connection failure, or if invalid credentials were configured. The print statement will be executed only if the data is persisted to DynamoDB, and will imply a successful operation of our command.
func write(key: String, value: String) throws {
//
}
We define a Codable
structure to save and load our data. This can later be extended with additional fields, like timestamps or any other metadata we would want to store.
struct Entry: Codable {
let PK: String
let value: String
}
Soto framework provides an AWSClient
class. We instruct the client to load AWS credentials from the default configuration file.
import SotoDynamoDBfunc write(key: String, value: String) throws {
let client = AWSClient(
credentialProvider: .configFile(
credentialsFilePath: "~/.aws/credentials"
),
httpClientProvider: .createNew
)
defer { try? client.syncShutdown() }
}
Then, we proceed to configure the DynamoDB client for accessing the table, and finally we proceed to write our key-value entry to the database. Make sure the region configured is the same where the table was created.
import SotoDynamoDBfunc write(key: String, value: String) throws {
let client = AWSClient(
credentialProvider: .configFile(
credentialsFilePath: "~/.aws/credentials"
),
httpClientProvider: .createNew
)
defer { try? client.syncShutdown() } let dynamoDB = DynamoDB(client: client, region: .uswest1)
let item = Entry(PK: key, value: value)
let input = DynamoDB.PutItemCodableInput<Entry>(
item: item,
tableName: "swift-demo"
)
_ = try dynamoDB.putItem(input).wait()
}
And that is it, we can now proceed to store any string data in our DynamoDB table as key-value pairs 👏. Let’s run some more tests:
$ swift run dynamodb-demo set hello world
hello=world
$ swift run dynamodb-demo set Swift 🧡
Swift=🧡
Since we have defined our value field as String
in our Swift structure, we can store any content that can be serialize as aString
, including JSON.
$ swift run dynamodb-demo set json '{"hello":"world!", "number":42}'
json={"hello":"world!", "number":42}
If you are following these steps, you should see the entries in the AWS DynamoDB console.
Reading data from DynamoDB
Given a primary key, reading data from DynamoDB is as simple as writing it.
Implement the command handler to call a read
method.
else if arguments.count == 3, arguments[1] == "get" {
let key = arguments[2]
let value = try read(key: key)
print(value ?? "")
}
This method will also use an AWSClient
and a DynamoDB
client, to read the entry from the table.
func read(key: String) throws -> String? {
let client = AWSClient(
credentialProvider: .configFile(
credentialsFilePath: "~/.aws/credentials"
),
httpClientProvider: .createNew
)
defer { try? client.syncShutdown() } let dynamoDB = DynamoDB(client: client, region: .uswest1)
let input = DynamoDB.GetItemInput(key: ["PK": .s(key)],
tableName: "swift-demo")
let result = try dynamoDB.getItem(input,
type: Entry.self).wait()
return result.item?.value
}
The result is Optional
, since users might ask to retrieve values for key that do not exist in our database. In this case, the method will return nil
. Similar to the write
method, this method will throw an exception if there were an error, like a broken network connection, invalid credentials, etc.
Let’s give it a try.
$ swift run dynamodb-demo get Swift
🧡$ swift run dynamodb-demo get hello
world$ swift run dynamodb-demo get json
{"hello":"world!", "number":42}
Fantastic! We can now write and read key-values from DynamoDB, using our AWS credentials.
What to do with this?
Well, that is entirely up to you! Being able to access DynamoDB from the command line opens a wide range of possibilities. Here are some ideas of things that could be done with this:
- Data processing: Processing large data files and storing generated information in DynamoDB
- Data migration: load data from relational databases and push to DynamoDB
- Distributed processing: have multiple command line processes, running in the same or different machines, reading/writing data to/from DynamoDB
- Given that Swift binaries can run on Amazon Linux, it wouldn't be too difficult to write workers in Swift and deploy them to EC2 or ECS, for data processing in the cloud.
The source code for this article can be found on GitHub: dynamodb-demo
Thank you very much for reading this, hopefully it was helpful and interesting.
If you liked it, please follow me 🙏. You can also follow me on Twitter and review my open source contributions on GitHub. Thanks!