これは、この機会のために考え出された教育的な例ではありません。以下の各コード スニペットは、現在リポジトリに存在する Aurabase ルート Cargo.toml とそのクレート マニフェストから取得したものです。これには、この記事のために再読した際に見つかった 2 つの矛盾も含まれており、公開前にこっそり修正するのではなく、そのまま文書化しています。
- Aurabase のルート Cargo ワークスペースは、18 の明示的なメンバー (
resolver = "2"および 1 つのCargo.lockの下に) 11 のサービス、aura-cliCLI、5 つの共有ライブラリ、およびaurabase-rsSDK を宣言します。 [workspace.dependencies]は共有バージョンを一元管理します。各クレートは、クレートがサイレントにオーバーライドする場合を除き、独自の番号を設定するのではなく、{ workspace = true }を使用して継承します。services/の下のフォルダーは、自動的にはワークスペースのメンバーになりません。membersリストは、非 Rust コードを除外できるように、グロブではなく明示的です。[profile.release]はワークスペース全体に 1 回だけ適用されます。panic = "unwind"のような選択は、11 個のサービスすべてに一度に適用されます。
サービスごとにデポが 1 つだけですか、それとも Cargo.lock が 1 つだけですか?
ワークスペース Cargo は、単一の Cargo.lock および単一のターゲット ディレクトリの下に複数のクレートをグループ化します。これは、Rust のパッケージ マネージャーがこの場合に提供する機能そのものです。それぞれが独自のリポジトリにある 11 個の個別の Rust バイナリは、一見するとより独立しているように見えます。実際には、これは 11 の異なる Cargo.lock、時間の経過とともに異なる可能性がある 11 のバージョン解決を意味し、2 つのサービスが同じバージョンのaxum または sqlxをコンパイルする保証はありません。
A Cargo workspace solves this at the package manager level, not the team discipline level. All members share a single Cargo.lock at the root: a common dependency is resolved once, to an identical version, for the entire graph. This is also what makes a cross-service refactor (changing a signature in aura-core, for example) visible to a single cargo build --workspace, rather than discovered service by service in production. This is one of the choices that distinguishes our 100% unified Rust core from a heterogeneous stack assembled service by service.
Cargo.toml ルート: リゾルバーとメンバー
すべては宣言 [workspace] とそのリスト membersから始まります。 Aurabase では、このリストは手書きであり、 services/*のような一般的なパターンによって生成されるのではなく、サービス、ツール、ライブラリなどの役割ごとにグループ化されています。
resolver = "2" は表面的な詳細ではありません。 Cargo の リゾルバー v2 は、 build-dependencies およびターゲット固有の依存関係 (target.'cfg(windows)'など) の機能をグラフの残りの部分から分離します。これらは最終バイナリに漏れなくなります。また、それを共有するワークスペースのすべてのメンバー間で同じ依存関係のバージョンを統一します。つまり、11 個の独立した解決ではなく、単一の axumが 1 回だけです。
workspace.package: 1 バージョン、1 エディション、原則として共有
[workspace.package] は、各クレートがコピーする代わりに version.workspace = true で継承できる共通フィールド (バージョン、エディション、作成者、ライセンス) を一度宣言します。
Aurabase の 11 のサービスはすべてこのパターンに従っています。 2 つのクレートが逸脱しています。これは、この記事の準備中に見つかった 2 つの矛盾のうちの 1 つ目です。aura-cli はハード コピーで version = "0.2.0" を宣言し、ライブラリ libs/aura-migrations は version = "0.1.0" を宣言しています。どちらもワークスペースの 0.1.1 とは異なります。
version.workspace = true はオプションで、フィールドごと、クレートごとに指定されます。自発的(たとえば、別途公開されたツール)または忘れてしまった場合でも、クレートが独自の番号を付けることを妨げるものはありません。ワークスペース監査では、継承を想定するのではなく、このフィールドをクレートごとにチェックする必要があります。
workspace.dependency: クレートがバイパスしない限り、真実のソース
[workspace.dependencies] は、複数のクレートによって共有される依存関係を一元化します。各サービスは、独自のバージョン制約を設定する代わりに、{ workspace = true } を使用してそれを参照します。
これが 2 番目の実際の矛盾です。ワークスペースは、バージョン 0.10の governor を集中管理しますが、 aura-gateway は、継承する代わりに独自の行 governor = "0.8" を再宣言します。ゲートウェイは、 aura-auth、 aura-ai、 aura-db および aura-functions が通過するときに、裸の governor クレートでレート制限を適用します。ミドルウェアのtower_governor。ワークスペースは相違を防ぐものではなく、比較する手間を掛けた場合に相違を可視化するだけです。
ただし、すべての依存関係が一元化されるに値するわけではありません。 wasmtime は [workspace.dependencies]のどこにも表示されません。1 つのクレート aura-functionsのみが WASM ランタイムに使用するため、ローカルで宣言されたままになります。適用するルール: 依存関係は、2 つ以上のクレートがそれを共有する前ではなく、共有した瞬間からワークスペース レベルに引き上げられます。
libs/ から services/: これは何に依存しますか
5 つの共有ライブラリ (aura-core、 aura-crypto、 aura-db-adapters、 aura-migrations、 aura-telemetry) は、 [workspace.dependencies] — aura-core = { path = "libs/aura-core" } — でパス依存関係として宣言され、各サービスは実際に必要なものを選択します。
結果として得られるグラフは、ファイル間で引き続き読み取り可能です。 aura-gateway は、5 つの内部ライブラリのうち 3 つ ( aura-core、 aura-crypto 、および aura-telemetry) にのみ依存します。 aura-db-adapters や aura-migrationsには触れずに、PostgREST サイドカーの JWT に署名し、そのトレースをエクスポートするだけで十分です。 aura-provisionerは aura-migrationsを追加します。プロジェクトの作成によって生じるスキーマの移行を再実行します。
最も狭いケースは aura-migratorで、これは CI/CD 移行を実行する専用バイナリです。 Aurabase 内部ライブラリの中で、そのプロダクション [dependencies] はaura-migrations のみをリストし、 aura-core はテストのために [dev-dependencies]にのみ表示されます。したがって、本番環境に配信されるバイナリにはaura-core コードは含まれません。これは cargo test中にのみ存在します。
1 つのクレート、複数のバイナリ: オーラリアルタイム部門
新しいデプロイ可能なプロセスが必要になるたびに、ワークスペースで新しいメンバーを作成する必要はありません。 aura-realtime はワークスペースの単一のメンバーのままですが、その Cargo.toml は、同じ共有 [lib]の周囲に 3 つの異なる [[bin]] テーブルを宣言します。
The manifesto itself documents the boundary between the two. cdc-worker captures Postgres changes by polling and publishes to NATS in pub/sub core (Client::publish_with_headers), without ever opening a WebSocket server — JetStream, in this same service, exclusively serves the cross-instance presence KV, not the CDC fan-out. ws-front only consumes NATS and holds WebSocket/SSE connections, without ever touching the CDC — the full details are in thereal-time engine documentation. Both binaries share the same RLS filtering code via [lib], but deploy and scale independently in Kubernetes. This is the right signal to choose several binaries in a crate rather than a new workspace member: same internal logic, different deployment topologies.
services/ 下のファイルは Cargo メンバーである必要はありません
aura-edge-runtime フォルダは Aurabase リポジトリに存在します。ただし、これには Cargo.toml は含まれておらず、TypeScript (index.ts、 envelope.ts) のみが含まれており、ルート ワークスペースの members リストのどこにも表示されません。
これがまさに、Aurabase の members リストが services/*のような汎用パターンに置き換えられるのではなく、手書きされる理由です。グロブはこの非 Rust フォルダーをワークスペースに含めようとしますが、結果として解決が失敗します。明示的なリストにより、同じ言語を話さないフォルダーが同じ親 services/の下に共存できます。
このレッスンでは、services/ のサブフォルダーをリストして Rust バックエンドのサービスをカウントすると、誤った数値が得られることを一般化します。ルート Cargo.toml のみが、ワークスペースで実際にコンパイルされる内容に関して権限を持ちます。
[profile.release]: ワークスペース全体に対する単一の設定
ワークスペースのルートで宣言された [profile.release] は、リリース モードでコンパイルされたすべてのメンバーに適用されます。調整するのは 1 か所のみで、11 か所ではありません。 Cargo は、利用可能なすべてのキーを コンパイル プロファイル参照に文書化します。 Aurabase は 5 つだけアクティブになります。
"abort" ではなく panic = "unwind" の選択は、ファイルのコメントに直接文書化されています。axum ハンドラーのパニックは tokio によってインターセプトされ、タスクは 500 を返し、プロセスは他の同時リクエストの処理を続行します。abort のパフォーマンスの向上は、クエリ間の分離を失う価値はありません。
ツールチェーンと重要なコマンドを凍結する
統合ワークスペースは依存関係のバージョンを設定しますが、コンパイラー自体のバージョンは設定しません。 rust-toolchain.tomlはルートで、リポジトリ内の cargo または rustc への直接呼び出しに対してツールチェーン (今日ではchannel = "1.93" ) をフリーズします。
このファイルが存在するのは、まさにサイレント ドリフトが発生したためです。リリース Docker イメージが凍結されたバージョンでコンパイルされている間に、CI アクションが rust-toolchain.tomlを読み取らずにその日の stable チャネルをインストールしました。現在の安定版 Rust ではコミットがすべてのテストに合格し、その後イメージのビルドで中断される可能性があります。マージ前ではなくマージ後に発見されます。 rustupは、あらゆる直接コマンドに対してこのファイルを尊重します。これは、CI ワークフローに影響を与えることなく、必要な唯一の修正でした。
最後に、マニフェストを実行する前に読み取る人のために説明します。 aura-cli クレートは、テーブル [[bin]] の名前が auraではなく aurabaseであるバイナリをコンパイルします。公開された npm ラッパー @aurabase/cliは、2 つのコマンド (aura と aurabase) を公開しており、どちらも同じスクリプトを指します。 [[bin]] Cargo の名前、クレートの名前、および npm ラッパーによって公開される名前は、3 つの別個のものです。他の 2 つからは何も推測できません。
チェックリスト: どこで何を宣言するか
Cargo ワークスペースにクレートを追加するたびに、5 つの決定事項が表示されます。上記で説明した Aurabase の例に基づいて、それぞれが宣言されている場所は次のとおりです。
| メンバー一覧 | [ワークスペース]メンバー | ルート — 明示的なリスト、決してグロブではない |
|---|---|---|
| 共有バージョン/エディション | [ワークスペース.パッケージ] | ルート — version.workspace = true、クレート別、オプション |
| 2 つ以上のクレートによって共有される依存関係 | [ワークスペース.依存関係] | ルート - 各クレートで { workspace = true } |
| 単一の消費者への依存 | クレートの[依存関係] | ワークスペースを経由せずにローカルで |
| ビルドプロファイル | [プロフィール.リリース] | ルートのみ — すべてのメンバーに適用されます |
既存の Cargo ワークスペース (自分のワークスペース、または引き継いだプロジェクトのワークスペース) を監査するには、上記のような矛盾を見つけるには 4 つのチェックで十分です。
[workspace.package].versionを各クレートのversionと比較します。異なる値は必ずしもバグではありませんが、文書化する価値があります。[workspace.dependencies]を各クレートによってローカルに宣言された依存関係と比較します。バージョンが異なる両側に存在する名前を見つけます。- ルート
Cargo.tomlのmembersリストをリポジトリ内の実際のサブフォルダーと比較します。membersにフォルダーが欠落していても、必ずしも見落としがあるわけではありません。 - コンパイルされた各バイナリ (
[[bin]] name) がクレート名または npm ラッパーによって公開される名前と一致すると仮定するのではなく、実際の名前を確認してください。