4.5 KiB
Architecture Decision Record: Higher-Order Topologies
Initial Author: Jean-Gabriel Gill-Couture
Initial Date: 2025-12-08
Last Updated Date: 2025-12-08
Status
Implemented
Context
Harmony models infrastructure as Topologies (deployment targets like K8sAnywhereTopology, LinuxHostTopology) implementing Capabilities (tech traits like PostgreSQL, Docker).
Higher-Order Topologies (e.g., FailoverTopology<T>) compose/orchestrate capabilities across multiple underlying topologies (e.g., primary+replica T).
Naive design requires manual impl Capability for HigherOrderTopology<T> per T per capability, causing:
- Impl explosion: N topologies × M capabilities = N×M boilerplate.
- ISP violation: Topologies forced to impl unrelated capabilities.
- Maintenance hell: New topology needs impls for all orchestrated capabilities; new capability needs impls for all topologies/higher-order.
- Barrier to extension: Users can't easily add topologies without todos/panics.
This makes scaling Harmony impractical as ecosystem grows.
Decision
Use blanket trait impls on higher-order topologies to automatically derive orchestration:
/// Higher-Order Topology: Orchestrates capabilities across sub-topologies.
pub struct FailoverTopology<T> {
/// Primary sub-topology.
primary: T,
/// Replica sub-topology.
replica: T,
}
/// Automatically provides PostgreSQL failover for *any* `T: PostgreSQL`.
/// Delegates to primary for queries; orchestrates deploy across both.
#[async_trait]
impl<T: PostgreSQL> PostgreSQL for FailoverTopology<T> {
async fn deploy(&self, config: &PostgreSQLConfig) -> Result<String, String> {
// Deploy primary; extract certs/endpoint;
// deploy replica with pg_basebackup + TLS passthrough.
// (Full impl logged/elaborated.)
}
// Delegate queries to primary.
async fn get_replication_certs(&self, cluster_name: &str) -> Result<ReplicationCerts, String> {
self.primary.get_replication_certs(cluster_name).await
}
// ...
}
/// Similarly for other capabilities.
#[async_trait]
impl<T: Docker> Docker for FailoverTopology<T> {
// Failover Docker orchestration.
}
Key properties:
- Auto-derivation:
Failover<K8sAnywhere>getsPostgreSQLiffK8sAnywhere: PostgreSQL. - No boilerplate: One blanket impl per capability per higher-order type.
Rationale
- Composition via generics: Rust trait solver auto-selects impls; zero runtime cost.
- Compile-time safety: Missing
T: Capability→ compile error (no panics). - Scalable: O(capabilities) impls per higher-order; new
Tauto-works. - ISP-respecting: Capabilities only surface if sub-topology provides.
- Centralized logic: Orchestration (e.g., cert propagation) in one place.
Example usage:
// ✅ Works: K8sAnywhere: PostgreSQL → Failover provides failover PG
let pg_failover: FailoverTopology<K8sAnywhereTopology> = ...;
pg_failover.deploy_pg(config).await;
// ✅ Works: LinuxHost: Docker → Failover provides failover Docker
let docker_failover: FailoverTopology<LinuxHostTopology> = ...;
docker_failover.deploy_docker(...).await;
// ❌ Compile fail: K8sAnywhere !: Docker
let invalid: FailoverTopology<K8sAnywhereTopology>;
invalid.deploy_docker(...); // `T: Docker` bound unsatisfied
Consequences
Pros:
- Extensible: New topology
AWSTopology: PostgreSQL→ instantFailover<AWSTopology>: PostgreSQL. - Lean: No useless impls (e.g., no
K8sAnywhere: Docker). - Observable: Logs trace every step.
Cons:
- Monomorphization: Generics generate code per T (mitigated: few Ts).
- Delegation opacity: Relies on rustdoc/logs for internals.
Alternatives considered
| Approach | Pros | Cons |
|---|---|---|
Manual per-T implsimpl PG for Failover<K8s> {..}impl PG for Failover<Linux> {..} |
Explicit control | N×M explosion; violates ISP; hard to extend. |
Dynamic trait objectsBox<dyn AnyCapability> |
Runtime flex | Perf hit; type erasure; error-prone dispatch. |
| Mega-topology trait All-in-one OrchestratedTopology |
Simple wiring | Monolithic; poor composition. |
| Registry dispatch Runtime capability lookup |
Decoupled | Complex; no compile safety; perf/debug overhead. |
Selected: Blanket impls leverage Rust generics for safe, zero-cost composition.
Additional Notes
- Applies to
MultisiteTopology<T>,ShardedTopology<T>, etc. FailoverTopologyinfailover.rsis first implementation.