Crate clean_dynamodb_store

Crate clean_dynamodb_store 

Source
Expand description

§AWS Clean DynamoDB Store

clean_dynamodb_store is a Rust library designed to follow clean architecture principles, offering a straightforward and efficient DynamoDB store implementation. It simplifies interactions with AWS DynamoDB, making it easier to perform common database operations such as inserting and deleting items in a DynamoDB table.

§Features

  • Easy-to-use asynchronous API for DynamoDB
  • Efficient client reuse following AWS SDK best practices
  • Supports basic DynamoDB operations like put (insert/update) and delete items
  • Input validation for table names and items/keys
  • Custom error types for better error handling
  • Built on top of aws-sdk-dynamodb for robust and up-to-date DynamoDB access
  • Designed with clean architecture principles in mind

§Prerequisites

Before you begin, ensure you have:

  • Rust 2024 edition or later
  • AWS account and configured AWS CLI or environment variables for AWS access

§Usage

Create a DynamoDbStore once and reuse it across operations for optimal performance.

The type-safe API works with your own structs using serde:

use clean_dynamodb_store::DynamoDbStore;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct User {
    id: String,
    name: String,
    age: u32,
}

#[derive(Serialize)]
struct UserKey {
    id: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create store once, reuse many times
    let store = DynamoDbStore::new().await?;

    // Put an item using a struct
    let user = User {
        id: "user123".to_string(),
        name: "John Doe".to_string(),
        age: 30,
    };
    store.put("users", &user).await?;

    // Get an item
    let key = UserKey { id: "user123".to_string() };
    let user: Option<User> = store.get("users", &key).await?;

    // Delete an item
    store.delete("users", &key).await?;

    Ok(())
}

§Table-Scoped API (Repository Pattern)

For implementing the repository pattern or working extensively with a specific table, you can create a table-bound store that eliminates the need to pass the table name on every operation:

use clean_dynamodb_store::DynamoDbStore;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct User {
    id: String,
    name: String,
}

#[derive(Serialize)]
struct UserKey {
    id: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let store = DynamoDbStore::new().await?;

    // Create table-bound stores for repository pattern
    let users = store.for_table("users");
    let orders = store.for_table("orders");

    // Use without passing table name
    let user = User { id: "123".into(), name: "John".into() };
    users.put(&user).await?;

    let key = UserKey { id: "123".into() };
    let user: Option<User> = users.get(&key).await?;

    users.delete(&key).await?;

    Ok(())
}

§Low-Level API

For advanced use cases, you can use the low-level HashMap API:

use clean_dynamodb_store::DynamoDbStore;
use aws_sdk_dynamodb::types::AttributeValue;
use std::collections::HashMap;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let store = DynamoDbStore::new().await?;

    // Put an item
    let mut item = HashMap::new();
    item.insert("id".to_string(), AttributeValue::S("user123".to_string()));
    item.insert("name".to_string(), AttributeValue::S("John Doe".to_string()));
    store.put_item("users", item).await?;

    // Delete an item
    let mut key = HashMap::new();
    key.insert("id".to_string(), AttributeValue::S("user123".to_string()));
    store.delete_item("users", key).await?;

    Ok(())
}

§AWS Lambda Usage

For AWS Lambda functions, initialize the store in main() before the handler to reuse the client across warm invocations:

use clean_dynamodb_store::DynamoDbStore;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct User {
    id: String,
    name: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize once during cold start
    let store = DynamoDbStore::new().await?;

    // Pass to handler - reused across warm invocations
    // lambda_runtime::run(service_fn(|event| handler(event, &store))).await
    Ok(())
}

// async fn handler(event: Event, store: &DynamoDbStore) -> Result<Response, Error> {
//     let user = User { id: event.id, name: event.name };
//     store.put("users", &user).await?;
//     Ok(Response::success())
// }

Re-exports§

pub use error::Error;
pub use error::Result;
pub use store::BatchGetResult;
pub use store::BatchWriteResult;
pub use store::DynamoDbStore;
pub use store::FailedItem;
pub use store::FailedKey;
pub use store::QueryResult;
pub use store::ScanResult;
pub use store::TableBoundStore;

Modules§

error
store