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.

Eneko Alonso
6 min readOct 17, 2020

In this article we will learn how to write and read data from AWS DynamoDB from a command line application in Swift.

Prerequisites

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 PackageDescription
let 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.

DynamoDB table content on AWS 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!

--

--

Eneko Alonso

Senior Engineer at MINDBODY. I specialize in Swift applications (mobile and server-side).