2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

この記事はRust+SvelteKit+CDKでRSS要約アプリを作ってみる Advent Calendar 2025の13日目の記事になります。

また、筆者が属している株式会社野村総合研究所のアドベントカレンダーもあるので、ぜひ購読ください。

はじめに

RustからDynamoDBを操作する場合、公式のAWS SDK for Rust (aws-sdk-dynamodb) が便利です。しかし、構造体とDynamoDBのAttributeValue(型付きJSONのようなもの)との変換までは実装されていません。そこで今回は、serde_dynamoというクレートを組み合わせて、Rustの構造体をそのままDynamoDBに保存・取得する方法を採用しました。

AWS SDK for Rustとserde_dynamo

AWS SDK for Rust

AWS公式が提供するSDKで、非同期処理(Tokio)を前提に設計されています。
aws_config::load_defaultsで設定を読み込み、aws_sdk_dynamodb::Clientを作成して操作します。

AttributeValueの壁

DynamoDBのアイテムはHashMap<String, AttributeValue>という形式で表現されます。
例えば、{"id": "123", "count": 1}というデータを保存するには、以下のような記述になります。

// SDKをそのまま使うと大変...
let item = HashMap::from([
    ("id".to_string(), AttributeValue::S("123".to_string())),
    ("count".to_string(), AttributeValue::N("1".to_string())),
]);

serde_dynamoによる解決

serde_dynamoを使うと、Serialize/Deserializeを実装したRustの構造体を、自動的にこのHashMap<String, AttributeValue>に変換してくれます。これにより、ビジネスロジックで扱う型とDB操作をシームレスに繋ぐことができます。

そもそもRustにはserdeという非常に便利なSerialize/Deserializeを扱えるクレートがあります。

SumaRSSでの実装

cdk/lambda/src/lib.rsDynamDbRepositoryクラスの実装を見てみます。

依存関係

[dependencies]
aws-config = "1"
aws-sdk-dynamodb = "1"
serde = { version = "1", features = ["derive"] }
serde_dynamo = { version = "4", features = ["aws-sdk-dynamodb+1"] }

実装コード(保存処理)

pub async fn put_item<T>(&self, item: &T) -> Result<(), Error>
where
    T: Serialize,
{
    // 1. 構造体をDynamoDB形式(HashMap)に変換
    let item_map = serde_dynamo::to_item(item).unwrap();

    // 2. SDKを使って保存
    self.client
        .put_item()
        .table_name(&self.table_name)
        .set_item(Some(item_map))
        .send()
        .await?;

    Ok(())
}

実装コード(取得処理)

pub async fn list_items<T>(&self) -> Result<Vec<T>, DynamDbListItemsError>
where
    T: DeserializeOwned,
{
    // 1. Scan実行
    let results = self.client
        .scan()
        .table_name(&self.table_name)
        .send()
        .await?;

    // 2. 結果を構造体のベクタに変換
    let items = results
        .items()
        .iter()
        .map(|item| {
            // AttributeValueのMapから構造体へ変換
            serde_dynamo::from_item(item.clone())
        })
        .collect::<Result<Vec<T>, _>>()?;

    Ok(items)
}

実装のポイント

保存時はto_itemを、取得時はfrom_itemを使うことで、DynamoDBのアイテムと構造体とを相互に変換可能です(実装例では無邪気に.unwrapを使っていますが本当はもっと丁寧にしたほうがいい気がします)。

put_item<T>list_items<T>のようにジェネリクスを使うことで、Article構造体でもFeed構造体でも、同じリポジトリクラスで扱えるようにしています。条件としてSerializeDeserializeOwnedトレイトを実装していることを要求することで型安全にしています。

SDKの初期化

SDKの初期化部分ではまずconfigを組み立てて、それをベースにクライアントを作成します。configの組み立て時にロジックを組むことで、ローカルで実行する時とLambdaで実行するときとで柔軟に切り替えることができます。

let config = get_sdk_config().await;

let dynamodb_config = if let Ok(endpoint_url) = std::env::var("AWS_ENDPOINT_URL") {
    debug!(endpoint_url = %endpoint_url, "Using custom DynamoDB endpoint");
    aws_sdk_dynamodb::config::Builder::from(&config)
        .endpoint_url(endpoint_url)
        .build()
} else {
    aws_sdk_dynamodb::config::Builder::from(&config).build()
};

let client = aws_sdk_dynamodb::Client::from_conf(dynamodb_config);

まとめ

aws-sdk-dynamodbserde_dynamoを組み合わせることで、Rustの型安全性を維持したまま、直感的にDynamoDBを操作できました。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?