キャディのバックエンドエンジニアをして働いている高藤です。
キャディではRustを使ったAPIサーバを開発しています。今回はその開発の過程で導入したcargo workspace
を使ったプロジェクト構成についてまとめました。
今回のアプリケーションについて
実装しているアプリケーションで使っている技術や設計手法などは弊社エンジニアが書いた別の記事もご参照下さい。
workspaceを使うようになるまでの経緯
開発初期、cargo new
コマンドで生成されたプロジェクトを以下のような構造にして実装していました。
application_name
├─ app
│ └─ main.rs
├─ src
│ ├─ domain/
│ │ ├─ aaa.rs
│ │ └─ ...etc
│ ├─ usecase/
│ │ ├─ bbb.rs
│ │ └─ ...etc
│ └─ infrastructure/
│ ├─ grpc/
│ │ └─ ...etc
│ └─ mq/
│ │ └─ ...etc
├─ Cargo.toml
ドメイン層などをディレクトリを使い階層構造でmoduleを配置しています。処理をどこに記述すべきかを理解しやすくするためこのような構成にしていました。この構造でプロジェクトが進むにつれ、各ディレクトリ内のmoduleは増え続けると共にビルド時間が増大し、開発の効率を悪化させる事象が発生しました。
cargo workspace の利用
上記の問題を解決するため、cargo workspace
という機能でプロジェクトを複数のcrateに分離しました。
workspaceを使うメリット
crateを分割するメリットとしては保守性や再利用性の向上ももちろんありますが、今回のケースとしてはビルド時間を少しでも短縮することが当初の目的でした。
なぜならRustのビルドツールcargo
では依存関係のないcrate
は並列にコンパイルする事が出来ます。
上記のケースではinfrastructure
の中にあるコードはdomain
,usecase
に依存しています。他方でinfrastructure
内部のgrpc
, mq
などの処理はお互いに依存はないため、分割することでコンパイル速度を向上させることが可能です。
workspaceを使ったプロジェクト構成
application_name
├─ app
│ ├─ src/main.rs
│ └─ Cargo.toml
├─ domain
│ ├─ src/...etc
│ └─ Cargo.toml
├─ usecase
│ ├─ src/...etc
│ └─ Cargo.toml
├─ grpc
│ ├─ src/...etc
│ └─ Cargo.toml
├─ mq
│ ├─ src/...etc
│ └─ Cargo.toml
├─ Cargo.toml
上記の構成では5つのcrate
に分割しています。
workspaceの作り方
application_name/Cargo.toml
を以下のように定義します。
[workspace]
members = [
"app",
"domain",
"usecase",
"grpc",
"mq",
]
workspace
配下に配置するcrateを上記の様にmembers
として記述をします。
それぞれのcrate
の中にはCargo.toml
を用意する必要があります。
なお、members
に記述のはpath
になるため、必ずしも同一階層に全てのcrate
を配置しなくても定義可能です。
例: ./infrastructure/grpc
, ./infrastructure/mq
のように定義することも可能。
workspace適用後の効果
今回のケースの場合、下記グラフの通り最終的に10分前後かかっていたビルド時間が、2分弱の時間で実行できるようになりました。
workspaceの使い方メモ
workspace
に関する詳細は各ドキュメント等を参考にしてください。簡単な説明となってしまいますが箇条書きでいくつか利用方法等をご紹介させてもらいます。
workspace
の中ではコンパイル成果物が格納されるtarget
ディレクトリはworkspace
直下に配置されます。(上記例だとapplication_name/target
)Cargo.lock
も同様にworkspace
直下に配置されます。これによりworkspace
配下のcrate
が依存するcrate
のバージョンを保証しています。workspace
を利用しているときも通常のプロジェクトと同様にcargo
コマンドでビルドを行うことが出来ます(cargo check
,cargo build
,cargo run
...etc)workspace
配下のcrate
にカレントディレクトリを変更してビルドを行った場合そのcrate
を対象にビルドができます。- カレントディレクトリを変更したくない場合は
--package
オプションを使ってビルドも可能です(cargo check --package domain
)
- カレントディレクトリを変更したくない場合は
- The Rust Programming Language ch14-03
- The Cargo Book
最後に
私達が開発するアプリケーションは現在16 crateまで分割しています。正直まだ分離させられる余地もあり、成長と共にビルド時間が増えたり、保守観点から分離すべきタイミングで分けるべきだと考えています。
また、参考までにRust製のservice meshであるlinkerd2-proxy
を確認すると55のcrateから構成されています。
このようにアプリケーションが成長し規模や複雑さに応じて簡単にworkspace
を使って分離できるのはかなり有用かと思っています。
ある程度の成長が予測されるアプリケーションなどは最初からworkspace
の構成を考えておくなどしておくと良いと思います。
参考: linkerd2-proxy