Orchestration¶
The orchestration module turns single-database migration deployment into a planned, multi-environment rollout. It is the runtime counterpart to the migrate CLI commands and is intended for scenarios where the same schema lives in several databases (per-tenant instances, dev / staging / prod tiers, blue / green pairs).
The module is gated behind the orchestration feature, which implies client.
Concepts¶
| Type | Role |
|---|---|
EnvironmentConfig | Connection details + metadata (name, connection, priority, tags, require_approval). |
EnvironmentRegistry | Process-wide async registry of named environments. |
DeploymentPlan | An ordered list of environment names, the migrations to apply, and strategy parameters. |
DeploymentStrategy | Async trait with a single deploy(plan) method. Concrete strategies vary in fan-out and failure handling. |
DeploymentCoordinator | Wraps an Arc<dyn DeploymentStrategy> and runs a plan against the registry. |
DeploymentResult | Per-environment outcome (status, migrations applied, error, duration). |
DeploymentStatus | Pending, InProgress, Success, Failed, RolledBack. |
HealthCheck | Reachability probe + migration-table existence check for an EnvironmentConfig. |
Built-in strategies¶
The strategies submodule exports four concrete DeploymentStrategy implementations, selectable by name through StrategyKind:
- Sequential runs environments one at a time and short-circuits on the first failure. The safe default.
- Parallel fans out to every environment concurrently with a caller-supplied
max_concurrentbound. - Rolling deploys in fixed-size batches; the next batch starts only after the previous one finishes.
- Canary deploys to a percentage of environments first, then fans out to the remainder only if the canary subset succeeded.
DeploymentCoordinator::with_strategy_label is the convenience constructor that resolves a StrategyKind plus its parameters (batch_size, canary_percentage, max_concurrent) to the concrete Arc<dyn DeploymentStrategy>.
Quick start¶
use std::sync::Arc;
use surql::connection::ConnectionConfig;
use surql::orchestration::{
DeploymentCoordinator, DeploymentPlan, EnvironmentConfig, EnvironmentRegistry,
strategies::SequentialStrategy,
};
let registry = EnvironmentRegistry::new();
let dev = EnvironmentConfig::builder("dev", ConnectionConfig::default()).build()?;
let prod = EnvironmentConfig::builder("prod", ConnectionConfig::default())
.require_approval(true)
.priority(100)
.build()?;
registry.register(dev).await;
registry.register(prod).await;
let coordinator = DeploymentCoordinator::new(
registry.clone(),
Arc::new(SequentialStrategy::new()),
);
let plan = DeploymentPlan::builder(registry)
.environments(["dev", "prod"])
.migrations(load_pending_migrations()?)
.build()?;
let results = coordinator.deploy(&plan).await?;
for (env, outcome) in results {
println!("{env}: {:?} ({} migrations)", outcome.status, outcome.migrations_applied);
}
coordinator.deploy(&plan) returns Result<HashMap<String, DeploymentResult>>. Per-environment failures are recorded in the map with status = DeploymentStatus::Failed; the top-level Result only errors when environments cannot be resolved, when the pre-flight health check fails, or when the strategy itself raises a fatal error.
Health checks¶
use surql::orchestration::{check_environment_health, verify_connectivity};
let env = registry.get("prod").await.expect("prod registered");
let status = check_environment_health(&env).await?;
if !status.is_healthy {
eprintln!("{} unhealthy: {:?}", status.environment, status.error);
}
let reachable = verify_connectivity(&env).await?;
HealthStatus is a struct (not an enum) carrying environment, is_healthy, can_connect, migration_table_exists, and an optional error message. check_environment_health is the end-to-end probe; verify_connectivity only confirms the client can open a session.
The coordinator will run verify_all_environments automatically when the plan's verify_health flag is set; unhealthy environments abort the deploy before any migration runs.