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

大規模プロダクトのための Cargo Workspaces ベストプラクティス

nawa
November 28, 2024
62

大規模プロダクトのための Cargo Workspaces ベストプラクティス

Rust.Tokyo 2024 の https://rust.tokyo/lineup/1 で発表スライドとして利用した資料です。

nawa

November 28, 2024
Tweet

Transcript

  1. 大規模プロダクトのための Cargo Workspaces ベストプラクティス Rust.Tokyo 2024 2024-11-30 Fairy Devices Inc.

    プロダクト開発部 名和 https://speakerdeck.com/mnawa/da-gui-mo-purodakutonotameno-cargo-workspaces-besutopurakuteisu
  2. 目次 1. Cargo Workspaces って何? 2. DB マイグレーション処理を途中から導入した事例 (special thanks:

    河野さん) 3. 大規模プロダクトの設計パターン 4. Workspaces を使って開発するときのオススメ 5. おわり 2
  3. プログラムを分ける方法といえば Cargo Workspaces の話の前に、Rust でプログラムをコンポーネント分けする方法は 2 個あります。 - モジュール (`mod`)

    を使う - 他のクレート (外部ライブラリー) に依存する 釈迦に説法している気がする…… 4 ※ 「コンポーネント」と言っているのはプログラムを部品に分けてく、くらいのニュアンスです。
  4. crates.io にないクレートに依存する 7 世の中で広く使われているような処理は crates.io にありますが、自社プロダクト向けの 処理などは当然 crates.io にはありません。 別の場所に

    clone したものに以下みたいに path で依存するか、git を指定で依存する とかするでしょうか? 共通処理だと異なる Git リポジトリーにあるのは良いけど、そのプロダクトに固有の処理 は同じリポジトリーに置きたいです。
  5. 定性的な感覚ですが、以下のようなメリットデメリットがあります。 モジュールのみで分ける場合 - 小さいプロダクトには向いてる - 依存関係がわかりにくくなる - コンパイルの時間が伸びる クレートに分ける場合 -

    設計レベルで管理しやすい - Orphan Rule がめんどくさい - クレート個々のコンパイル時間は短くなるが、 リンクの時間が伸びる モジュールとクレートどちらを使うべきか 10
  6. 前提 12 - sqlx を使って DB アクセスしてるプロダクトがある - AWS App

    Runner で動いていて、Amazon ECR (Docker Registry) にイメージを push したらデプロイされる - DB スキーマのマイグレーションは手動で AWS Batch を動かして diesel CLI を実 行している PR マージ push by Actions trigger DB マイグレーション (diesel CLI) アプリデプロイ 手動実行
  7. DB マイグレーションを自動で実行するようにしたい。ついでに sqlx でマイグレーションするようにしたい。 - EventBridge で自動でトリガーする手もあるけど、プロセス起動時に DB マイグ レーション処理を埋め込みたい

    - sqlx が便利すぎるから、diesel → sqlx に移行したい 1. マイグレーション処理を埋め込みたい。 (`sqlx::migrate!()`) 2. マイグレーション定義ファイルのディレクトリー構成を sqlx に合わせる必要あり。          3. マイグレーション状況を記録したテーブルの移行が必要。 13 PR マージ push by Actions アプリデプロイ & (起動時に) DB マイグ レーション trigger
  8. DB マイグレーションを自動で実行するようにしたい。ついでに sqlx でマイグレーションするようにしたい。 - EventBridge で自動でトリガーする手もあるけど、プロセス起動時に DB マイグ レーション処理を埋め込みたい

    - sqlx が便利すぎるから、diesel → sqlx に移行したい 1. マイグレーション処理を埋め込みたい。 (`sqlx::migrate!()`) 2. マイグレーション定義ファイルのディレクトリー構成を sqlx に合わせる必要あり。          3. マイグレーション状況を記録したテーブルの移行が必要。 14 PR マージ push by Actions アプリデプロイ & (起動時に) DB マイグ レーション trigger sqlx に専用処理がある ディレクトリー構成を mv して合わせるだけ (シンボリックリンク貼れば両対応もできる) プロダクト固有の課題のわりに、特定領域の処理じゃね???
  9. trait で依存関係の逆転を行う 20 SOLID 原則の DIP ですが、DB 関連などの部分から core の部分に依存するには

    core に trait を用意して外 側の層のクレートではそれを実装すると良いです。 ※ HTTP 関連の部分→ core の部分は単に呼び出すだけ foo-core foo-server foo-db 永続化処理のための条件 を集めた trait を用意する 左の trait を impl する
  10. 分けたクレートへ core から依存したい状況もある 24 すべての SDK が Rust で提供されてるわけはないので (e.g.

    社内の別システム (CWS))、自分である程度の 実装が必要なものは lib 層、DIP 層とかに分けると良い状況もあります。 foo-core foo-server foo-cws-api foo-cws データ型だけ依存したい 特化処理だけ依存したい core の要求 (trait) を impl する DIP 層 システムに特化した処理を実 装する lib 層 ※ 社内の別システムも Rust で書かれているなら、そのシステムのメンバークレートとしてクライアントライブラリーがあると便利そう
  11. まとめ: Workspaces を使って開発するときのオススメ 26 - Procedural macros (derive マクロなど) はそもそもクレートを分ける必要がある。同じリポジトリーで管理

    したい。 - (root 以外の) メンバークレートを一つのディレクトリー に入れる - クレート名に同一の接頭辞を付ける - cargo-release でワークスペース内の複数クレートを順番に publish できる - バージョンの共通化 - 依存関係の一覧化
  12. proc macro 27 Procedural macros を使うには、そもそもクレートを分けて、 Cargo.toml で `proc-macro =

    true` を書く必要があ ります。 別のリポジトリーで管理したくないので、ワークスペースに入れると便利です。 https://github.com/serde-rs/serde
  13. クレート名に同一の接頭辞を付ける 29 分かりやすさのためでもありますが、 Docker Image をビルドするときに、 依存クレートだけビルドしたレイヤーを 作るのがやりやすくなります。 FROM rust:1-bookworm

    AS builder COPY Cargo.toml ./ COPY crates/my-crate-foo/Cargo.toml ./crates/my-crate-foo/ COPY crates/my-crate-bar/Cargo.toml ./crates/my-crate-bar/ RUN mkdir ./crates/my-crate-{foo,bar}/src/ \ && touch ./crates/my-crate-{foo,bar}/src/lib.rs \ && cargo build --release \ && rm -r ./crates/my-crate-*/src/ \ ./target/release/deps/my_crate* \ ./target/release/deps/libmy_crate_*.rlib # --- COPY ./crates/my-crate-foo/src ./crate/my-crate-foo/src COPY ./crates/my-crate-bar/src ./crate/my-crate-bar/src RUN cargo build --release Dockerfile 依存クレート以外の 中間生成物の削除が楽
  14. 複数クレートの publish が面倒 → cargo-release 30 どちらかというと OSS 向けの話ですが、crates.io へ

    Workspaces で管理してるリポジトリーのクレートを publish するのって結構めんどくさいです。 (private レジストリーを使う場合には関わってきそう。 ) Workspaces のクレートはそれぞれで依存するからこそ同じリポジトリーで管理したいのですが、 crates.io に依 存先のクレートがない状態だと publish に失敗します。クレート A から B に依存してる場合、A → B の順番に は publish できない。B → A で publish する必要がある。 cargo-release を使うと順番通りに publish してくれるし、バージョンを上げたり、 Git のタグを打ったりも自動で やってくれます。 ※ private レジストリー: https://github.com/rust-lang/cargo/wiki/Third-party-registries
  15. メタデータの共有 (1/2): バージョンの共通化 31 (cargo-release でバージョンも上げてくれるんですが、 ) クレートのバージョンなど [package] セクションの値を

    [workspace.package] で一箇所で管理できます。 CI で毎週金曜日に最新の Rust 処理系で fmt などを行って PR を出してくれるようにしてるのですが、その中で rust-version も上げて最新環境で開発できるようにしています。
  16. メタデータの共有 (2/2): 依存関係の一覧化 32 (DB マイグレーションのくだりでちょっと話した内容ですが、 ) [workspace.dependencies] を使うと依存するク レートのバージョンを共通化できます。ここで、原則

    [workspace.dependencies] に全部書くようにしてしまうの が良いと思っています。 - root の Cargo.toml → プロダクトが直接依存しているクレートの一覧 - 個々の Cargo.toml → そのクレートの依存 ※ 異なるメンバークレートで異なるバージョンの同じクレートに依存したいときは、 package = "[package]" を使うと別名で import できます。という か、違うバージョンの sha2 を使ってることが [workspace.depedencies] に統一したときに発覚して使いました。
  17. まとめ 34 - Cargo Workspaces の概要を説明 - 後から導入した事例を紹介 - 設計の流れを紹介

    - Workspaces を使って開発する際の Tips を紹介 - Procedural macros (derive マクロなど) はそもそもクレートを分ける必要がある。同じリポジトリーで管理したい。 - (root 以外の) メンバークレートを一つのディレクトリーに入れる - クレート名に同一の接頭辞を付ける - cargo-release でワークスペース内の複数クレートを順番に publish できる - バージョンの共通化 - 依存関係の一覧化