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
62
大規模プロダクトのための 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
320
Rust で型安全な SPA 開発
mnawa
0
720
Featured
See All Featured
Faster Mobile Websites
deanohume
305
30k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
230
52k
Gamification - CAS2011
davidbonilla
80
5.1k
Product Roadmaps are Hard
iamctodd
PRO
50
11k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
26
1.9k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
19
2.3k
Building a Scalable Design System with Sketch
lauravandoore
460
33k
Build your cross-platform service in a week with App Engine
jlugia
229
18k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
33
2k
[RailsConf 2023] Rails as a piece of cake
palkan
53
5.1k
No one is an island. Learnings from fostering a developers community.
thoeni
19
3.1k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
33
2.7k
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