aymond
A batteries-included client wrapper for DynamoDB
Builds upon the existing AWS SDK DynamoDB client, providing a high-level interface, somewhat akin to the DynamoDB Enhanced Java Client. Utilizes code generation to create tailored interfaces for items, pushing as much validation as possible to compile time.
Quickstart
Items are described by structs:
Table instances are used for interactions:
let aymond = new_with_local_config;
let table = new;
// Create a table in local DynamoDB, based on our item schema
table.create.await.expect;
Write items with put:
let it = Car ;
table.put.item.send.await.expect;
Read items with get, query, scan (and more!):
let req = table.get.make.model;
let _: = req.send.await.expect;
Usage
Attribute types
aymond maps each attributes Rust type to the corresponding DynamoDB type
Scalars
| Rust | DynamoDB |
|---|---|
| String | AttributeValue::S |
| i32 | AttributeValue::N |
| Vec<u8> | AttributeValue::B |
| HashSet<String> | AttributeValue::Ss |
| HashSet<Vec<u8>> | AttributeValue::Bs |
| Vec<String> | AttributeValue::L |
| Nested items | AttributeValue::M |
Nested items
Operations
Most relevant DynamoDB actions should be implemented. Below is based on the Car struct from quickstart. Since function names are code-generated from an item's attributes, examples can't be entirely generic.
Get
let req = table.get.make.model;
let _: = req.send.await.unwrap;
Put
let it = Car ;
table.put.item.send.await.unwrap;
Query
let req = table.query.make.model_begins_with;
let _: = req.send.await.map.collect.await;
Scan
let req = table.scan;
let _: = req.send.await.map.collect.await;
Update
let _: = table
.update
.make
.model
.expression
.send
.await;
Batch get
let _: = table
.batch_get
.make_and_model
.make_and_model
.send
.await
.unwrap;
Batch write
let _: = table
.batch_write
.put
.delete
.make
.model
.send
.await;
Advanced features
Transactions
The Aymond instance can be used to build and send transactions using TransactWriteItems. These can span tables and use the same builders as individual requests:
let _: = aymond
.tx
.update
.put
.delete
.send
.await;
Optimistic locking
Items can define a #[aymond(version)] attribute:
#[aymond(item, table)]
struct Item {
#[aymond(hash_key)]
id: String,
#[aymond(version)]
ver: i3,
}
When set, operations like table.delete().item(<>) and table.put().item(<>) will enforce version checking.
In the case of put(), the version number will be incremented during write -- for example, if the input to put() had ver: 6, we'd generate a condition expression that ensures DynamoDB currently has 6 and overwrite it with 7. Version 0 is treated as a sentinel value that ensures object creation.
If you want to bypass versioning on a specific request, you can do that with a condition expression -- table.put().item(<>).condition(|c| c.disable_versioning()).
Condition/update expressions
Both types of expressions support:
- Deep nesting with list and map access
- Type awareness: string properties will have a
begins_withmethod while numeric types wont
To illustrate, take for example this item:
Expressions like these could be used, seeking into both lists and nested items:
table
.update
.name
.expression
.condition
Development
The tests assume that DynamoDB local is available on port 8000 -- start it with any container runtime:
The integration tests can be ran with: