AWS SDK for Rust の Developer Preview が発表されました。
examplesにDynamoDB操作関連のソースコードを参考にしつつ…
今回は PutItem・GetItem・UpdateItem・DeleteItem を使って、
CRUDを実装してみたいと思います。
AWS SDK for Rust を使って、CRUDを実装する
- put_item でデータ登録
- get_item でデータを取得
- update_item でデータを更新
- delete_item でデータを削除
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のみの指定なので
条件を増やして複雑な条件での更新や削除にも挑戦してみたいなぁと思います。
コメント