microde-microservice
The Rust runtime is published as the microde-microservice crate. Add it with
cargo add microde-microservice
The API uses the same composition model as the TypeScript runtime while using
Rust-native traits, owned futures, and Result-based errors.
API index
Runtime
Microserviceinstalls modules, binds relationships, and coordinates execution.MicroserviceModuledefines the module lifecycle.MicroserviceContextlets modules request a stop or terminate immediately.MicroserviceStopRequestdescribes an orderly shutdown request.MicroserviceExecutionResultreports the final outcome.MicroserviceStateandModuleKinddescribe runtime behavior.MicroserviceErroris the error type returned by the runtime.
Modules and composition
ModuleFutureis the owned future returned by lifecycle methods.ModuleHandleandModuleInstanceIdidentify an exact installed module.Portdeclares a provider contract;Providerexports a value for it.DependencyandReferencedeclare relationship slots.SetupContextandRunContextresolve bound values in lifecycle methods.
Supporting traits and descriptors
RelationshipKind,RelationshipDescriptor, andRelationshipSlotdescribe relationship metadata used by composition.RunRelationshipis implemented by slots accepted byRunContext.MicroserviceContextHandleis an independently owned module context.
Module implementation
Modules declare their kind and override lifecycle methods as needed:
use microde_microservice::{MicroserviceModule, ModuleFuture, ModuleKind};
struct Worker;
impl MicroserviceModule for Worker {
const KIND: ModuleKind = ModuleKind::Passive;
fn run(&mut self) -> ModuleFuture {
Box::pin(async {
println!("worker complete");
Ok(())
})
}
}
initialize, setup_with_context, run_with_context, stop, teardown,
shutdown, and cleanup have default implementations. Override only the
phases the module needs.
Installation and composition
install_named returns an opaque ModuleHandle for one stable module instance.
Handles are required when binding relationships and cannot be used across
different Microservice values.
let database = service.install_named("database", |_| DatabaseModule::new())?;
let orders = service.install_named("orders", |_| OrdersModule::new())?;
service.bind(&orders, &orders_database, &database)?;
Calling run seals the composition. Bindings, providers, and dependency cycles
are validated before any lifecycle callback starts.
Ports, providers, and relationships
Port<T> identifies a typed provider contract. A module returns its owned
Provider values from providers(). Consumers declare Dependency<T> and
Reference<T> relationship slots from a port:
let database_port = Port::<Database>::new("database");
let database_slot = Dependency::new("database", database_port.clone());
let peer_slot = Reference::new("peer", database_port);
Dependencies participate in the lifecycle DAG and are available through
SetupContext and RunContext. References do not affect lifecycle order and
are available only through RunContext, so reference cycles are allowed.
Lifecycle contexts
Use SetupContext::use_dependency during setup and
RunContext::use_relationship during run:
fn setup_with_context(&mut self, context: SetupContext) -> ModuleFuture {
let database: Database = context.use_dependency(&self.database_slot)?;
Box::pin(async move { Ok(()) })
}
fn run_with_context(&mut self, context: RunContext) -> ModuleFuture {
let peer: Database = context.use_relationship(&self.peer_slot)?;
Box::pin(async move { Ok(()) })
}
Lifecycle order is dependency-first with stable instance IDs as tie-breakers. Teardown, shutdown, and cleanup use the exact reverse order.