百名山 API を開発しました | mountix APICLICK !

AWS SDK for RustでDynamoDBへのCRUDを実装する

  • URLをコピーしました!

AWS SDK for Rust の Developer Preview が発表されました。

examplesにDynamoDB操作関連のソースコードを参考にしつつ…

今回は PutItemGetItemUpdateItemDeleteItem を使って、
CRUDを実装してみたいと思います。

AWS SDK for Rust を使って、CRUDを実装する

  1. put_item でデータ登録
  2. get_item でデータを取得
  3. update_item でデータを更新
  4. delete_item でデータを削除

Developer Preview のため、本番環境での使用は避けたほうが無難でしょう。

目次

CRUDを実装する

検証環境

  • macOS:Big Sur 11.6
  • Rust:1.57.0
  • AWS SDK for Rust:v0.2.0

環境変数の設定

認証情報の取得は環境変数からおこなうため、以下の環境変数を設定します。

  • AWS_DEFAULT_REGION
  • AWS_SECRET_ACCESS_KEY
  • AWS_ACCESS_KEY_ID

詳しくは、READMEで確認してください。

テーブルの作成

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
        }
    ]
}

CRUDを実装する

Cargo.toml

まずは依存クレートを設定します。

[dependencies]
aws-config = "0.2.0"
aws-sdk-dynamodb = "0.2.0"
tokio = { version = "1", features = ["full"] }

main.rs

まず、全コードをお見せします。

Create、Read、Update、Deleteでそれぞれ関数を作成しています。

use aws_sdk_dynamodb::{Client};
use aws_sdk_dynamodb::model::{AttributeValue, ReturnValue, AttributeValueUpdate, AttributeAction};

#[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
        }
    }
}

環境変数に設定した認証情報を取得するため、configを生成します。
その後、DynamoDBのクライアントを生成します。

生成したクライアントそれぞれの関数に渡し、実行しています。

以下、実行結果です。

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

Create: PutItem

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
        }
    }
}

.table_name()でテーブル名を指定し、.send()でリクエストを送信します。
ここらへんの仕様は、scanやqueryと同じです。

.put_item()では、.item()で作成するデータを生成していきます。

PutItemの.return_values()では、ReturnValue::AllOldもしくはReturnValue::None
引数で渡すことが可能です。

PutItemでは、既存のデータが存在する場合、上書きとなります。
上書きが起きた場合にALL_OLD、つまり上書きされる前のデータを受け取ることができます。

Read: GetItem

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
        }
    }
}

Idで検索するため、.key()で条件を指定します。

IdはNumber型なので、AttributeValue::N()を使用します。

Update: UpdateItem

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
        }
    }
}

.update_item()では、AttributeValueUpdateが必要なので、最初に生成しておきます。

サンプルでは、PutItemで登録した仙道さんのポジションを”SF”から”SF/PG”に更新しています。
.key()で対象レコードを検索し、.attribute_updates()で更新する値を渡します。

.action()で、PUTを指定しています。
他にもADDやDELETEなどが存在します。

PUT – デフォルト

属性が既に存在しない場合は、属性とその値がアイテムに追加される。
属性が既に存在する場合は、新しい値に置き換えられる。

ADD

属性が既に存在しない場合、属性とその値がアイテムに追加される。
属性が存在する場合は、数字が加算される、要素が追加される。

詳しくは、AttributeActionのドキュメントを確認してみてください。

.return_values()でレスポンスの内容を決定します。
ReturnValue::AllNewを指定し、新しいデータを受け取るようにします。

Delete: DeleteItem

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
        }
    }
}

1番シンプルかもしれません。
.key()で削除するレコードのIdを指定します。

まとめ

AttributeValueUpdate::builder()のようにビルダーを生成する部分で
若干戸惑いましたが、ドキュメントを読むことで解決できました。

やはり、頼るべきは公式ドキュメントですね!

UpdateItemやDeleteItemは、プライマリーキーであるIdのみの指定なので
条件を増やして複雑な条件での更新や削除にも挑戦してみたいなぁと思います。

著:初田直也, 著:山口聖弘, 著:吉川哲史, 著:豊田優貴, 著:松本健太郎, 著:原将己, 著:中村謙弘, 著:フォルシア株式会社
¥1,782 (2022/10/05 16:03時点 | Amazon調べ)

この記事が気に入ったら
フォローしてね!

  • URLをコピーしました!

コメント

コメントする

目次