Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rustで管理しない開発用データの作り方

Koji Onishi
January 25, 2023

 Rustで管理しない開発用データの作り方

登壇資料

イベント
Rust、何もわからない... #6
https://estie.connpass.com/event/270226/

紹介したライブラリ
https://crates.io/crates/cder
https://github.com/estie-inc/cder

Koji Onishi

January 25, 2023
Tweet

More Decks by Koji Onishi

Other Decks in Programming

Transcript

  1. 「人気アーティスト」のデータが必要だから、 artists.id=1の持つアルバムの評価を5に変更し ておくよ アプリケーション上のデータの意味も日々かわる 11 artists id name city_id 1

    The Beatles 2 2 Eric Clapton 1 3 U2 4 4 Oasis 3 albums id title rating is_new 1 Revolver 4.5 false 2 Definitely Maybe 3.5 true 3 October 3 false 4 Unplugged 5 false 「新着アルバム」画面を出す必要があるから albums.id=2を新着扱いにするよ
  2. ①データ・コードをSQLで定義する create table countries ( id int not null, name

    varchar(100) not null ); insert into countries (id, name) values (1, 'Afghanistan'); insert into countries (id, name) values (2, 'Albania'); insert into countries (id, name) values (3, 'Algeria'); insert into countries (id, name) values (4, ‘Andorra’); insert into countries (id, name) values (5, ‘Angola’); … create table users ( id int not null, name varchar(256) not null, country_id int not null ); insert into users (id, name, country_id) values (1, 'Bob', 15); insert into users (id, name, country_id) values (2, 'Alice', 33); … 15 一般的にはIDを固定して外部キーを指定するため マジックナンバー管理になり、可読性は低い
  3. ②データ・コードをプログラムで管理する 構造体(モデル)定義 その永続化を行う アプリケーションコード 16 DB データ定義 インスタンス組み立て + 永続化のためのグルーコード

    + • プログラマが自由に定義・カスタマイズしやすい • 言語機能やDSLを利用できるため、可読性・メンテナビリティは相対的に高い
  4. まとめ 導入コスト データ管理コスト コード管理コスト SQLで定義 ◎ ✖ ✖ コード定義 ◎

    (ライブラリのサポート前提) ▲ 一部アプリケーションコードが 流用可能 ダンプ インフラ側の対応がいる セキュリティ面の検討など ◎ ◎ 19
  5. まとめ 導入コスト データ管理コスト コード管理コスト SQLで定義 ◎ ✖ ✖ コード定義 ◎

    (ライブラリのサポート前提) ▲ 一部アプリケーションコードが 流用可能 ダンプ インフラ側の対応がいる セキュリティ面の検討など ◎ ◎ 20
  6. 見通しが悪くメンテが辛そう・・ 23 let pool = setup_mysql().await?; // userを作成する処理 let user_id1

    = User::insert(&pool, &UserInput{ first_name: "Taro".to_string(), last_name: "Yamada".to_string(), email: "[email protected]".to_string(), } ).await?; let user_id2 = User::insert(&pool, &UserInput{ first_name: "Jane".to_string(), last_name: "Conley".to_string(), email: "[email protected]".to_string(), } ).await?; let user_id3 = User::insert(&pool, &UserInput{ first_name: "Jack".to_string(), last_name: "Johnson".to_string(), email: "[email protected]".to_string(), } ).await?; let user_id3 = User::insert(&pool, &UserInput{ first_name: "Foo".to_string(), last_name: "Bar".to_string(), email: "[email protected]".to_string(), } ).await?; // …snip // productを作成する処理 let product_id1 = Product::insert(&pool, &ProductInput { name: "keyboard".to_string(), price: 100000, } ).await?; let product_id2 = Product::insert(&pool, &ProductInput { name: "microphone".to_string(), price: 20000, } ).await?; let product_id3 = Product::insert(&pool, &ProductInput { name: "guitar".to_string(), price: 50000, } ).await?; // dealerを作成する処理 let dealer_id1 = Dealer::insert(&pool, &DealerInput { name: "MacroSoft".to_string(), country: "US".to_string(), contract_type: 3, } ).await?; let dealer_id2 = Dealer::insert(&pool, &DealerInput { name: "April Computer".to_string(), country: "JP".to_string(), contract_type: 2, } ).await?; // 購入記録を作成する処理 let purchase_id1 = Purchase::insert(&pool, &PurchaseInput { buyer_id: user_id2, dealer_id: dealer_id1, product_id: product_id1, amount: 3, discount: None, tax: 14550, net_price: 314550, purchased_at: NaiveDateTime::parse_from_str("2022-02-01 21:23:45", "%Y-%m-%d %H:%M:%S")?, } ).await?; let purchase_id3 = Purchase::insert(&pool, &PurchaseInput { buyer_id: user_id2, dealer_id: dealer_id2, product_id: product_id2, amount: 1, discount: None, tax: 3300, net_price: 23330, purchased_at: NaiveDateTime::parse_from_str("2022-02-01 18:55:20", "%Y-%m-%d %H:%M:%S")?, } ).await?; let purchase_id3 = Purchase::insert(&pool, &PurchaseInput { buyer_id: user_id3, dealer_id: dealer_id1, product_id: product_id2, amount: 2, discount: Some(5000), tax: 3333, net_price: 38888, purchased_at: NaiveDateTime::parse_from_str("2022-08-02 11:20:15", "%Y-%m-%d %H:%M:%S")?, } ).await?; User構造体に新しいenum が生えました! Purchase構造体の責務が 大きすぎるから分割しま す〜
  7. シードデータの表現 28 # Companyデータ Denali: name: "有限会社デナリ" FitzRoy: name: "FitzRoy

    & Company" Estie: name: "株式会社エスティ" # Userデータ Alice: name: Alice email: "[email protected]" company_id: ${{ REF(Denali) }} Bob: name: Bob email: "[email protected]" country_code: 81 company_id: ${{ REF(FitzRoy) }} Dev: name: Developer email: ${{ ENV(DEV_EMAIL:-"[email protected]") }} country_code: 44 company_id: ${{ REF(Estie) }} pub struct Company { pub name: String, } pub struct User { pub name: String, pub email: String, pub country_code: Option<u8>, pub company_id: i64, } こんな感じでデータ部分だけを定義したい こういう構造体があったとして・・
  8. シードデータの表現 29 # Companyデータ Denali: name: "有限会社デナリ" FitzRoy: name: "FitzRoy

    & Company" Estie: name: "株式会社エスティ" # Userデータ Alice: name: Alice email: "[email protected]" company_id: ${{ REF(Denali) }} Bob: name: Bob email: "[email protected]" country_code: 81 company_id: ${{ REF(FitzRoy) }} Dev: name: Developer email: ${{ ENV(DEV_EMAIL:-"[email protected]") }} country_code: 44 company_id: ${{ REF(Estie) }} pub struct Company { pub name: String, } pub struct User { pub name: String, pub email: String, pub country_code: Option<u8>, pub company_id: i64, } Company保存時に名前とidの対応を保 存しておき、実行時に置換
  9. シードデータの表現 30 # Companyデータ Denali: name: "有限会社デナリ" FitzRoy: name: "FitzRoy

    & Company" Estie: name: "株式会社エスティ" # Userデータ Alice: name: Alice email: "[email protected]" company_id: ${{ REF(Denali) }} Bob: name: Bob email: "[email protected]" country_code: 81 company_id: ${{ REF(FitzRoy) }} Dev: name: Developer email: ${{ ENV(DEV_EMAIL:-"[email protected]") }} country_code: 44 company_id: ${{ REF(Estie) }} pub struct Company { pub name: String, } pub struct User { pub name: String, pub email: String, pub country_code: Option<u8>, pub company_id: i64, } 実行時に環境変数と置換 (なければ [email protected]にフォールバック)
  10. グルーコード部分のインターフェース 33 // 挿入結果のラベルとidの対応を保持する let mut seeder = DatabaseSeeder::new(); seeder.populate("companies.yml",

    |input: Company| { // // Companyを挿入する関数 // Fn<Company> -> Result<i64> })?; seeder.populate("users.yml", |input: User| { // // Userを挿入する関数 // Fn<User> -> Result<i64> })?; シード固有ではない共通処理はなるべくまとめ、グルーコード部分を小さく保ちたい 共通する定型処理をまとめたい • ファイルの読み取り • 埋め込みタグの解決(置換) • yamlのデシリアライズ • 一つ一つのレコードをブロックに渡し • ラベルとブロックの評価結果を紐付けして保存 • 後のラベルの解決に使う
  11. グルーコード 34 let mut seeder = DatabaseSeeder::new(); // non-asyncブロック(関数)を利用する場合 seeder.populate("users.yml",

    |input: User| { User::insert(pool, input) })?; let mut seeder = DatabaseSeeder::new(); // asyncブロック(関数)を利用する場合 seeder.populate_async("users.yml", |input: User| { Box::pin(async move { User::insert(pool, input).await }) }) .await?; 非同期関数も扱えるとさらによい