rust-backend について
Windmill の `backend/` で Rust を書くためのパターンガイドです。エラー処理、SQLx、JSON、非同期処理、コード構成に加え、プロジェクト固有のテレメトリー規則を扱います。
主な機能
- 01Result によるエラー処理と panic 回避
- 02SQLx のクエリとトランザクションのパターン
- 03JSON と Serde の処理指針
- 04非同期処理と Mutex の選択
- 05モジュール構成と rust-analyzer によるナビゲーション
- 06機能テレメトリーと Axum ハンドラーの規則
活用シーン
- 01Windmill の backend API の実装
- 02互換性を考慮した SQLx クエリの作成
- 03非同期・並行処理コードの確認
- 04機能利用テレメトリーイベントの追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ windmill-labs/ windmill --skill rust-backendインストール後、Claude Code で/rust-backendを入力して読み込まれたか確認します。
詳細
Windmill の Rust パターン
backend/ で Rust コードを書く際は、Windmill 固有のパターンを適用してください。
エラー処理
windmill_common::error の Error を使用してください。Result<T, Error> または JsonResult<T> を返します。
use windmill_common::error::{Error, Result};
pub async fn get_job(db: &DB, id: Uuid) -> Result<Job> {
sqlx::query_as!(Job, "SELECT id, workspace_id FROM v2_job WHERE id = $1", id)
.fetch_optional(db)
.await?
.ok_or_else(|| Error::NotFound("job not found".to_string()))?;
}
ライブラリコードでパニックを発生させないでください。.unwrap() はコンパイル時に保証される場合にのみ使用してください。
SQLx のパターン
SELECT * は使用しないでください。常に列を明示的に指定してください。ワーカーが API バージョンに追いついていない場合の後方互換性に不可欠です。
// Correct
sqlx::query_as!(Job, "SELECT id, workspace_id, path FROM v2_job WHERE id = $1", id)
// Wrong — breaks when columns are added
sqlx::query_as!(Job, "SELECT * FROM v2_job WHERE id = $1", id)
N+1 を避けるため、バッチ操作を使用してください。
// Preferred — single query with IN clause
sqlx::query!("SELECT ... WHERE id = ANY($1)", &ids[..]).fetch_all(db).await?
複数ステップの操作にはトランザクションを使用してください。すべてのクエリをパラメーター化してください。
JSON の処理
確認せずに JSON を保存または受け渡す場合は、serde_json::Value よりも Box<serde_json::value::RawValue> を優先してください。
pub struct Job {
pub args: Option<Box<serde_json::value::RawValue>>,
}
JSON の確認または変更が必要な場合にのみ、serde_json::Value を使用してください。
Serde の最適化
#[derive(Serialize, Deserialize)]
pub struct Job {
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_job: Option<Uuid>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
#[serde(default)]
pub priority: i32,
}
非同期処理と並行処理
非同期ランタイムをブロックしないでください。CPU 負荷の高い処理には spawn_blocking を使用してください。
let result = tokio::task::spawn_blocking(move || expensive_computation(&data)).await?;
Mutex の選択:データ保護には std::sync::Mutex(または parking_lot::Mutex)を優先してください。ロックを .await ポイントをまたいで保持する場合にのみ、tokio::sync::Mutex を使用してください。
チャネルには tokio::sync::mpsc(有界)を使用してください。非同期コンテキストでは std::thread::sleep を避けてください。
モジュール構成と可視性
- 可能な場合は
pubではなくpub(crate)を使用してください - 機能に応じて適切なクレートに新しいコードを配置してください
- API エンドポイントは、ドメインごとに整理して
windmill-api/src/に配置してください - 共有機能は
windmill-common/src/に配置してください
コードのナビゲーション
定義への移動、参照の検索、型情報の確認には、必ず rust-analyzer LSP を使用してください。モジュールパスを推測しないでください。
機能テレメトリー
windmill-api-workspaces/src/workspaces.rs の FEATURE_USAGE_KINDS は許可リストです。ここにない
(feature, kind) の組み合わせは、valid_feature_usage_event によって何もエラーを返さずに
continue で破棄され、ルートは 204 を返します。フロントエンドにカウンターを追加しても、
ここに登録しなければ何も記録されません。docs/feature-telemetry.md を参照してください。
Axum ハンドラー
関数シグネチャ内でエクストラクターを直接分解してください。
async fn process_job(
Extension(db): Extension<DB>,
Path((workspace, job_id)): Path<(String, Uuid)>,
Query(pagination): Query<Pagination>,
) -> Result<Json<Job>> { ... }