1️⃣

AWS SDK for Rust v1 で DynamoDB を操作する

2023/11/29に公開

AWS SDK for Rust が GA

AWS re:Invent 2023 に合わせて、AWS SDK for Rust が GA されましました。

Rustacean にとって、待望の GA ではないでしょうか🥳🎉

https://aws.amazon.com/jp/blogs/developer/announcing-general-availability-of-the-aws-sdk-for-rust/

https://github.com/awslabs/aws-sdk-rust

Getting Started with AWS SDK for Rust

過去に AWS SDK for Rust の Developer Preview が発表された際に試したものを
正式リリース版で試してみたいと思います。

なお、公式のサンプルコードは examples にあります。

事前準備

DynamoDB に Users テーブルを作成し、「スラムダンク 湘北高校のメンバー」を登録します。

  • テーブル名:Users
  • パーティションキー:Id(Number)
  • ソートキー:なし
サンプルデータ:スラムダンク 湘北高校のメンバー
{
    "users": [
        {
            "Id": 1,
            "Name": "桜木花道",
            "Grade": 1,
            "Position": "PF",
            "Number": 10,
            "Height": 189,
            "Weight": 83
        },
        {
            "Id": 2,
            "Name": "流川楓",
            "Grade": 1,
            "Position": "SF",
            "Number": 11,
            "Height": 187,
            "Weight": 75
        },
        {
            "Id": 3,
            "Name": "赤木剛憲",
            "Grade": 3,
            "Position": "C",
            "Number": 4,
            "Height": 197,
            "Weight": 93
        },
        {
            "Id": 4,
            "Name": "宮城リョータ",
            "Grade": 2,
            "Position": "PG",
            "Number": 7,
            "Height": 168,
            "Weight": 59
        },
        {
            "Id": 5,
            "Name": "三井寿",
            "Grade": 3,
            "Position": "SG",
            "Number": 14,
            "Height": 184,
            "Weight": 70
        },
        {
            "Id": 6,
            "Name": "木暮公延",
            "Grade": 3,
            "Position": "SF",
            "Number": 5,
            "Height": 178,
            "Weight": 62
        }
    ]
}

また、Rust の実行環境に以下の環境変数をセットします。

  • AWS_DEFAULT_REGION
  • AWS_SECRET_ACCESS_KEY
  • AWS_ACCESS_KEY_ID

Cargo.toml

Cargo.toml に SDK を追加します。

Cargo.toml
[dependencies]
aws-config = { version = "1", features = ["behavior-version-latest"] }
aws-sdk-dynamodb = "1"
tokio = { version = "1", features = ["full"] }

DynamoDB のデータを Scan する

過去のコードをそのまま実行することができました!

main.rs
use aws_sdk_dynamodb::{Client};

#[tokio::main]
async fn main() {
    let config = aws_config::load_from_env().await;
    let client = Client::new(&config);

    let resp = client.scan().table_name("Users").send().await;
    match resp {
        Ok(scan_output) => {
            if let Some(items) = scan_output.items {
                for item in items {
                    println!("{:?}", item);

                    // Idを取り出す
                    if let Some(attr_val) = item.get("Id") {
                        if let Ok(id_val) = attr_val.as_n() {
                            if let Ok(id) = id_val.parse::<u32>() {
                                println!("{}", id);
                            }
                        }
                    }

                    // Nameを取り出す
                    if let Some(attr_val) = item.get("Name") {
                        if let Ok(name) = attr_val.as_s() {
                            println!("{}", name.to_string());
                        }
                    }
                }
            }
        },
        Err(e) => {
            println!("{}", e);
        }
    }
}

実行結果

{"Id": N("3"), "Weight": N("93"), "Height": N("197"), "Name": S("赤木剛憲"), "Number": N("4"), "Position": S("C"), "Grade": N("3")}
3
赤木剛憲
{"Id": N("2"), "Number": N("11"), "Height": N("187"), "Name": S("流川楓"), "Position": S("SF"), "Grade": N("1"), "Weight": N("75")}
2
流川楓
{"Number": N("7"), "Grade": N("2"), "Weight": N("59"), "Id": N("4"), "Name": S("宮城リョータ"), "Position": S("PG"), "Height": N("168")}
4
宮城リョータ
{"Grade": N("3"), "Name": S("木暮公延"), "Height": N("178"), "Id": N("6"), "Number": N("5"), "Position": S("SF"), "Weight": N("62")}
6
木暮公延
{"Position": S("PF"), "Height": N("189"), "Id": N("1"), "Name": S("桜木花道"), "Number": N("10"), "Grade": N("1"), "Weight": N("83")}
1
桜木花道
{"Height": N("184"), "Position": S("SG"), "Grade": N("3"), "Id": N("5"), "Number": N("14"), "Name": S("三井寿"), "Weight": N("70")}
5
三井寿

DynamoDB のデータを Query 検索する

修正が必要な箇所は 1 箇所でした。

modeltypes に変更されたようです。

main.rd
use aws_sdk_dynamodb::{Client};
- use aws_sdk_dynamodb::model::{AttributeValue};
+ use aws_sdk_dynamodb::types::AttributeValue;

#[tokio::main]
async fn main() {
    let config = aws_config::load_from_env().await;
    let client = Client::new(&config);

    let resp = client
        .query()
        .table_name("Users")
        .key_condition_expression("#key = :value".to_string())
        .expression_attribute_names("#key".to_string(), "Id".to_string())
        .expression_attribute_values(":value".to_string(), AttributeValue::N("1".to_string()))
        .send()
        .await;
    match resp {
        Ok(query_output) => {
            if let Some(items) = query_output.items {
                for item in items {
                    println!("{:?}", item);

                    // Idを取り出す
                    if let Some(attr_val) = item.get("Id") {
                        if let Ok(id_val) = attr_val.as_n() {
                            if let Ok(id) = id_val.parse::<u32>() {
                                println!("{}", id);
                            }
                        }
                    }
                    // Nameを取り出す
                    if let Some(attr_val) = item.get("Name") {
                        if let Ok(name) = attr_val.as_s() {
                            println!("{}", name.to_string());
                        }
                    }
                }
            }
        }
        Err(e) => {
            println!("{}", e);
        }
    }
}

実行結果

{"Grade": N("1"), "Position": S("PF"), "Number": N("10"), "Height": N("189"), "Id": N("1"), "Weight": N("83"), "Name": S("桜木花道")}
1
桜木花道

DynamoDB のデータの CRUD(PutItem, GetItem, UpdateItem, DeleteItem) を実装する

上記と同じで修正が必要な箇所は 1 箇所で、modeltypes に変更されたようです。

main.rs
use aws_sdk_dynamodb::{Client};
- use aws_sdk_dynamodb::model::{AttributeValue, ReturnValue, AttributeValueUpdate, AttributeAction};
+ use aws_sdk_dynamodb::types::{AttributeAction, AttributeValue, AttributeValueUpdate, ReturnValue};

#[tokio::main]
async fn main() {
    let config = aws_config::load_from_env().await;
    let client = Client::new(&config);

    let user = User {
        id: 7,
        name: "仙道彰".to_string(),
        grade: 2,
        position: "SF".to_string(),
        number: 7,
        height: 190,
        weight: 79
    };

    // データ登録
    if create(&client, &user).await {
        println!("Create 完了");
    }

    // データ取得
    if read(&client, &user.id.to_string()).await {
        println!("Read 完了");
    }

    // データ更新
    let target_id = "7".to_string();
    let new_position = "SF/PG".to_string();
    if update(&client, &target_id, &new_position).await {
        println!("Update 完了");
    }

    // データ削除
    if delete(&client, &user.id.to_string()).await {
        println!("Delete 完了");
    }
}

struct User {
    id: u32,
    name: String,
    grade: u32,
    position: String,
    number: u32,
    height: u32,
    weight: u32
}

async fn create(client: &Client, user: &User) -> bool {
    match client
        .put_item()
        .table_name("Users")
        .item("Id", AttributeValue::N(user.id.to_string()))
        .item("Name", AttributeValue::S(user.name.to_string()))
        .item("Grade", AttributeValue::N(user.grade.to_string()))
        .item("Position", AttributeValue::S(user.position.to_string()))
        .item("Number", AttributeValue::N(user.number.to_string()))
        .item("Height", AttributeValue::N(user.height.to_string()))
        .item("Weight", AttributeValue::N(user.weight.to_string()))
        .return_values(ReturnValue::AllOld)
        .send()
        .await
    {
        Ok(put_item_output) => {
            println!("{:?}", put_item_output);
            true
        },
        Err(e) => {
            println!("{}", e);
            false
        }
    }
}

async fn read(client: &Client, id: &String) -> bool {
    match client
        .get_item()
        .table_name("Users")
        .key("Id".to_string(), AttributeValue::N(id.to_string()))
        .send()
        .await {
        Ok(get_item_output) => {
            if let Some(item) = get_item_output.item {
                println!("{:?}", item);

                // Idを取り出す
                if let Some(attr_val) = item.get("Id") {
                    if let Ok(id_val) = attr_val.as_n() {
                        if let Ok(id) = id_val.parse::<u32>() {
                            println!("{}", id);
                        }
                    }
                }

                // Nameを取り出す
                if let Some(attr_val) = item.get("Name") {
                    if let Ok(name) = attr_val.as_s() {
                        println!("{}", name.to_string());
                    }
                }
            }
            true
        }
        Err(e) => {
            println!("{}", e);
            false
        }
    }
}

async fn update (client: &Client, id: &String, position: &String) -> bool {
    let attr_val_up = AttributeValueUpdate::builder()
        .action(AttributeAction::Put)
        .value(AttributeValue::S(position.to_string()))
        .build();

    match client
        .update_item()
        .table_name("Users")
        .key("Id", AttributeValue::N(id.to_string()))
        .attribute_updates("Position", attr_val_up)
        .return_values(ReturnValue::AllNew)
        .send()
        .await
    {
        Ok(put_item_output) => {
            println!("{:?}", put_item_output);
            true
        },
        Err(e) => {
            println!("{}", e);
            false
        }
    }
}

async fn delete(client: &Client, id: &String) -> bool {
    match client
        .delete_item()
        .table_name("Users")
        .key("Id", AttributeValue::N(id.to_string()))
        .send()
        .await
    {
        Ok(delete_item_output) => {
            println!("{:?}", delete_item_output);
            true
        },
        Err(e) => {
            println!("{}", e);
            false
        }
    }
}

実行結果

PutItemOutput { attributes: None, consumed_capacity: None, item_collection_metrics: None, _request_id: Some("MB70NB9IRSBNVMNLM7NJUQJV6NVV4KQNSO5AEMVJF66Q9ASUAAJG") }
Create 完了
{"Position": S("SF"), "Weight": N("79"), "Height": N("190"), "Id": N("7"), "Number": N("7"), "Name": S("仙道彰"), "Grade": N("2")}
7
仙道彰
Read 完了
UpdateItemOutput { attributes: Some({"Name": S("仙道彰"), "Id": N("7"), "Height": N("190"), "Grade": N("2"), "Number": N("7"), "Weight": N("79"), "Position": S("SF/PG")}), consumed_capacity: None, item_collection_metrics: None, _request_id: Some("ATMP9H54EGPE4EL2NCO5R66IHFVV4KQNSO5AEMVJF66Q9ASUAAJG") }
Update 完了
DeleteItemOutput { attributes: None, consumed_capacity: None, item_collection_metrics: None, _request_id: Some("K5H5BO3OBONSVBP8J6HR0J3GM3VV4KQNSO5AEMVJF66Q9ASUAAJG") }
Delete 完了

まとめ

Developer Preview v0.2.0 で実装した DynamoDB 関連のコードがほぼ修正なしに実行できました。

SDK が GA されたことで、Rust での開発も活発になると良いですね🙌

参考

コラボスタイル Developers

Discussion