Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

shared-libs

English | 简体中文

Shared Rust libraries for application services. The workspace provides a typed core vocabulary and reusable boundaries for request entry, messaging, governance, and evidence.

Status

service-interface-boundary is the most complete crate in the workspace. It hosts a service-supplied Axum router, exposes health endpoints, gates business traffic by readiness, and coordinates managed startup and shutdown. The other boundary crates currently provide their initial contracts and no-op adapters.

Workspace Crates

Crate Responsibility Status
shared-libs-core Typed IDs, trace context, deadlines, and errors. Usable foundation
service-interface-boundary HTTP request entry, health, readiness, drain, and lifecycle protection. Usable foundation
messaging-reliability-boundary Message envelopes and publisher / consumer contracts. Initial contract
governance-control-boundary Governed-control vocabulary. Initial contract
evidence-boundary Evidence publication vocabulary. Initial contract

Service Interface Boundary

service-interface-boundary is the application process entry point. A service builds its domain state and Axum Router; the boundary hosts that router and provides runtime protection around it.

One HTTP listener serves both business routes and platform endpoints:

Path Purpose
/ops/live Reports whether the process is live.
/ops/ready Reports whether the service may receive new business traffic.
/ops/health Returns lifecycle, readiness, and drain state.
Any other path Reaches the service-supplied business router only when ready.

The default listener is 0.0.0.0:8080. Reserve the /ops prefix for the boundary; do not use it for business routes.

Quick Start

Add the workspace crates to a service:

[dependencies]
async-trait = "0.1"
axum = "0.8"
service-interface-boundary = { path = "../shared-libs/crates/service-interface-boundary" }
shared-libs-core = { path = "../shared-libs/crates/shared-libs-core" }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Create the business router, then start the interface boundary:

use axum::{Router, routing::get};
use service_interface_boundary::ServiceInterfaceBoundary;

async fn ping() -> &'static str {
    "pong"
}

let app = Router::new().route("/ping", get(ping));
let started = ServiceInterfaceBoundary::start("order-service", app).await?;

// Initialize dependencies, restore state, and start business loops first.
started.mark_ready();

Use ServiceInterfaceBoundary::start_on(service_name, app, addr) to override the listener address in local tests or special deployments.

Managed Runtime

For a long-running service, prefer ServiceInterfaceBoundary::serve(...). Implement ServiceRuntime in the business service:

use async_trait::async_trait;
use service_interface_boundary::{ServiceLifecycle, ServiceRuntime};
use shared_libs_core::CoreResult;

struct MatchingRuntime;

#[async_trait]
impl ServiceRuntime for MatchingRuntime {
    async fn start(&mut self, lifecycle: ServiceLifecycle) -> CoreResult<()> {
        // Recover state and start business loops.
        let _ = lifecycle;
        Ok(())
    }

    async fn begin_drain(&mut self) -> CoreResult<()> {
        // Stop accepting business work and reach a domain-safe point.
        Ok(())
    }

    async fn stop(&mut self) -> CoreResult<()> {
        // Release business resources after drain completes.
        Ok(())
    }
}

Then assemble the router and runtime in the executable:

ServiceInterfaceBoundary::serve("matching-service", app, runtime).await?;

Lifecycle

The service begins in Starting and is live but not ready. serve(...) marks the service ready after ServiceRuntime::start(...) succeeds. Manual startup uses StartedServiceInterface directly:

started.mark_ready();
started.mark_not_ready("database.unavailable");
started.begin_draining("shutdown.requested");
started.mark_stopped();

Business requests receive 503 Service Unavailable while the service is starting, not ready, draining, or stopped. The /ops/* routes remain available until the HTTP server shuts down.

When using serve(...), Ctrl-C or Unix SIGTERM starts this sequence:

begin_draining
-> ServiceRuntime::begin_drain
-> wait up to 30 seconds for business drain and admitted HTTP handlers
-> graceful HTTP shutdown
-> ServiceRuntime::stop
-> mark_stopped

ServiceRuntime::begin_drain owns business work that outlives an HTTP request, such as matching loops, queued commands, database transactions, and message consumers.

Current Limitations

  • AdmissionPolicy is a contract only; automatic request-envelope creation and policy middleware are not implemented yet.
  • Authentication, authorization, idempotency enforcement, deadlines, and standardized error responses are not implemented yet.
  • Metrics, logs, traces, and dependency-health methods are extension points, not concrete adapters.
  • WebSockets, streaming responses, and detached tasks need explicit service-specific drain policies.
  • start(...) and start_on(...) are manual APIs. When not using serve(...), call wait_for_in_flight_requests() and shutdown_http().

Development

cargo fmt --all --check
cargo test --workspace
cargo clippy --workspace -- -D warnings

Keep business state, governance decisions, and message-offset ownership outside the service interface boundary. Add focused tests for every lifecycle, routing, or request-admission behavior.

License

This repository does not currently declare a license. Add an OSI-approved license before distributing it as an open-source package.

About

Reusable Rust application boundaries for service interfaces, lifecycle management, messaging reliability, governance, and evidence.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages