Skip to main content

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

Modules and composition

Supporting traits and descriptors

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.