Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
大規模プロダクトのための Cargo Workspaces ベストプラクティス
Search
nawa
November 28, 2024
0
170
大規模プロダクトのための Cargo Workspaces ベストプラクティス
Rust.Tokyo 2024 の
https://rust.tokyo/lineup/1
で発表スライドとして利用した資料です。
nawa
November 28, 2024
Tweet
Share
More Decks by nawa
See All by nawa
Cargo Workspaces のススメ
mnawa
0
490
Rust で型安全な SPA 開発
mnawa
0
780
Featured
See All Featured
What’s in a name? Adding method to the madness
productmarketing
PRO
23
3.5k
GraphQLの誤解/rethinking-graphql
sonatard
71
11k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
44
2.4k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
26
2.9k
The Cult of Friendly URLs
andyhume
79
6.5k
Optimising Largest Contentful Paint
csswizardry
37
3.3k
Visualization
eitanlees
146
16k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
YesSQL, Process and Tooling at Scale
rocio
173
14k
Why Our Code Smells
bkeepers
PRO
336
57k
Gamification - CAS2011
davidbonilla
81
5.4k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
50
5.5k
Transcript
大規模プロダクトのための Cargo Workspaces ベストプラクティス Rust.Tokyo 2024 2024-11-30 Fairy Devices Inc.
プロダクト開発部 名和 https://speakerdeck.com/mnawa/da-gui-mo-purodakutonotameno-cargo-workspaces-besutopurakuteisu
目次 1. Cargo Workspaces って何? 2. DB マイグレーション処理を途中から導入した事例 (special thanks:
河野さん) 3. 大規模プロダクトの設計パターン 4. Workspaces を使って開発するときのオススメ 5. おわり 2
Cargo Workspaces って何? 3
プログラムを分ける方法といえば Cargo Workspaces の話の前に、Rust でプログラムをコンポーネント分けする方法は 2 個あります。 - モジュール (`mod`)
を使う - 他のクレート (外部ライブラリー) に依存する 釈迦に説法している気がする…… 4 ※ 「コンポーネント」と言っているのはプログラムを部品に分けてく、くらいのニュアンスです。
モジュールを使う モジュール (`mod`) はクレートの中でプログラムを分けて可視性を制御する仕組みで す。 5
他のクレートに依存する 世の中で広く使われているような処理は、外部ライブラリーとして crates.io にアップされ ていて Cargo を使って再利用できます。 6
crates.io にないクレートに依存する 7 世の中で広く使われているような処理は crates.io にありますが、自社プロダクト向けの 処理などは当然 crates.io にはありません。 別の場所に
clone したものに以下みたいに path で依存するか、git を指定で依存する とかするでしょうか? 共通処理だと異なる Git リポジトリーにあるのは良いけど、そのプロダクトに固有の処理 は同じリポジトリーに置きたいです。
そもそも同じリポジトリー内に置いて path で依存したら良いの では? 8 ↓ このときに、クレートをまとめて管理できる機能が Cargo Workspaces!
モジュールとクレート、ワークスペース 9 ワークスペース クレート モジュール コンパイル単位 はクレート クレート クレート ・・・
クレート ワークスペースの中にメンバーとしてクレートがあります。
定性的な感覚ですが、以下のようなメリットデメリットがあります。 モジュールのみで分ける場合 - 小さいプロダクトには向いてる - 依存関係がわかりにくくなる - コンパイルの時間が伸びる クレートに分ける場合 -
設計レベルで管理しやすい - Orphan Rule がめんどくさい - クレート個々のコンパイル時間は短くなるが、 リンクの時間が伸びる モジュールとクレートどちらを使うべきか 10
途中から導入した事例: DB マイグレーションを プロセス起動時に行う処理を実装する 11 Special Thanks 河野さん
前提 12 - sqlx を使って DB アクセスしてるプロダクトがある - AWS App
Runner で動いていて、Amazon ECR (Docker Registry) にイメージを push したらデプロイされる - DB スキーマのマイグレーションは手動で AWS Batch を動かして diesel CLI を実 行している PR マージ push by Actions trigger DB マイグレーション (diesel CLI) アプリデプロイ 手動実行
DB マイグレーションを自動で実行するようにしたい。ついでに sqlx でマイグレーションするようにしたい。 - EventBridge で自動でトリガーする手もあるけど、プロセス起動時に DB マイグ レーション処理を埋め込みたい
- sqlx が便利すぎるから、diesel → sqlx に移行したい 1. マイグレーション処理を埋め込みたい。 (`sqlx::migrate!()`) 2. マイグレーション定義ファイルのディレクトリー構成を sqlx に合わせる必要あり。 3. マイグレーション状況を記録したテーブルの移行が必要。 13 PR マージ push by Actions アプリデプロイ & (起動時に) DB マイグ レーション trigger
DB マイグレーションを自動で実行するようにしたい。ついでに sqlx でマイグレーションするようにしたい。 - EventBridge で自動でトリガーする手もあるけど、プロセス起動時に DB マイグ レーション処理を埋め込みたい
- sqlx が便利すぎるから、diesel → sqlx に移行したい 1. マイグレーション処理を埋め込みたい。 (`sqlx::migrate!()`) 2. マイグレーション定義ファイルのディレクトリー構成を sqlx に合わせる必要あり。 3. マイグレーション状況を記録したテーブルの移行が必要。 14 PR マージ push by Actions アプリデプロイ & (起動時に) DB マイグ レーション trigger sqlx に専用処理がある ディレクトリー構成を mv して合わせるだけ (シンボリックリンク貼れば両対応もできる) プロダクト固有の課題のわりに、特定領域の処理じゃね???
ワークスペースで特化処理用のクレートを足す 15 でライブラリークレートを作って、Cargo.toml に以下を追加します。
クレート達の依存するクレートのバージョンを合わせたい 16 マイグレーションを行うクレートも、元々あるクレートも同じ sqlx に依存したいです。
大規模プロダクトの設計パターン 17
大規模なプロダクトの構成を考える 18 よく見る図。 ※ https://medium.com/swlh/clean-architecture-a-little-introduction-be3eac94c5d1
関心を分離する 19 ざっくりブロック図で Web アプリの設計を考えると、 HTTP 関連の部分、DB 関連の部分、ドメインロジック (core) の部分とかが分かれてると嬉しいです。それぞれでクレートに分けることを考えてみます。
foo-core foo-server foo-db
trait で依存関係の逆転を行う 20 SOLID 原則の DIP ですが、DB 関連などの部分から core の部分に依存するには
core に trait を用意して外 側の層のクレートではそれを実装すると良いです。 ※ HTTP 関連の部分→ core の部分は単に呼び出すだけ foo-core foo-server foo-db 永続化処理のための条件 を集めた trait を用意する 左の trait を impl する
外部のサービスを扱う 21 外部サービスなどに特化した処理をクレートとして切り出すと、 core 部分はドメインロジックに集中できます。 foo-core foo-server 色々 foo-db foo-cws
※ CWS は THINKLET の基盤システム。私が開発してるのはそのユーザーアプリの立ち位置のもの。
エントリーポイントを増やす 22 HTTP が分かれていると、CLI ツールを別に作るとかやりやすいです。開発向けのツールや運用ツールを作る ときに、部品を再利用しやすくなります。 foo-core foo-server 色々 foo-db
foo-cws foo-tools (CLI)
個々で依存関係を管理できる 23 それぞれのクレートに Cargo.toml があるので、クレート毎に依存関係を管理できます。それぞれの Cargo.toml を見ると、ある程度何やってるかわかるのも利点です。 foo-core foo-server 色々
foo-db foo-cws foo-tools (CLI) ↳clap ↳serde, axum ↳serde, reqwest ↳diesel
分けたクレートへ core から依存したい状況もある 24 すべての SDK が Rust で提供されてるわけはないので (e.g.
社内の別システム (CWS))、自分である程度の 実装が必要なものは lib 層、DIP 層とかに分けると良い状況もあります。 foo-core foo-server foo-cws-api foo-cws データ型だけ依存したい 特化処理だけ依存したい core の要求 (trait) を impl する DIP 層 システムに特化した処理を実 装する lib 層 ※ 社内の別システムも Rust で書かれているなら、そのシステムのメンバークレートとしてクライアントライブラリーがあると便利そう
Workspaces を使って開発するときのオススメ 25
まとめ: Workspaces を使って開発するときのオススメ 26 - Procedural macros (derive マクロなど) はそもそもクレートを分ける必要がある。同じリポジトリーで管理
したい。 - (root 以外の) メンバークレートを一つのディレクトリー に入れる - クレート名に同一の接頭辞を付ける - cargo-release でワークスペース内の複数クレートを順番に publish できる - バージョンの共通化 - 依存関係の一覧化
proc macro 27 Procedural macros を使うには、そもそもクレートを分けて、 Cargo.toml で `proc-macro =
true` を書く必要があ ります。 別のリポジトリーで管理したくないので、ワークスペースに入れると便利です。 https://github.com/serde-rs/serde
メンバークレートを一つのディレクトリーに入れる 28 OSS では直下にあることが多いです。ライブラリーとしてはそれで良さそうですが、サービスだとどうしても ファイ ルが増えてくるので (IaC のファイルとか)、メンバークレートは同じディレクトリーに置くと管理が楽だと思ってい ます。members では
`*` が使えるので、まとめることもできます。 ./Cargo.toml ディレクトリー構造
クレート名に同一の接頭辞を付ける 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 依存クレート以外の 中間生成物の削除が楽
複数クレートの 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
メタデータの共有 (1/2): バージョンの共通化 31 (cargo-release でバージョンも上げてくれるんですが、 ) クレートのバージョンなど [package] セクションの値を
[workspace.package] で一箇所で管理できます。 CI で毎週金曜日に最新の Rust 処理系で fmt などを行って PR を出してくれるようにしてるのですが、その中で rust-version も上げて最新環境で開発できるようにしています。
メタデータの共有 (2/2): 依存関係の一覧化 32 (DB マイグレーションのくだりでちょっと話した内容ですが、 ) [workspace.dependencies] を使うと依存するク レートのバージョンを共通化できます。ここで、原則
[workspace.dependencies] に全部書くようにしてしまうの が良いと思っています。 - root の Cargo.toml → プロダクトが直接依存しているクレートの一覧 - 個々の Cargo.toml → そのクレートの依存 ※ 異なるメンバークレートで異なるバージョンの同じクレートに依存したいときは、 package = "[package]" を使うと別名で import できます。という か、違うバージョンの sha2 を使ってることが [workspace.depedencies] に統一したときに発覚して使いました。
まとめ 33
まとめ 34 - Cargo Workspaces の概要を説明 - 後から導入した事例を紹介 - 設計の流れを紹介
- Workspaces を使って開発する際の Tips を紹介 - Procedural macros (derive マクロなど) はそもそもクレートを分ける必要がある。同じリポジトリーで管理したい。 - (root 以外の) メンバークレートを一つのディレクトリーに入れる - クレート名に同一の接頭辞を付ける - cargo-release でワークスペース内の複数クレートを順番に publish できる - バージョンの共通化 - 依存関係の一覧化
ご清聴ありがとうございました。 宣伝: Fairy Devices は Rust.Tokyo 2024 のゴールドスポンサーです。 35