Skip to content

Latest commit

 

History

History
145 lines (104 loc) · 5.35 KB

CONTRIBUTING.md

File metadata and controls

145 lines (104 loc) · 5.35 KB

Contributing to modrun

What modrun is

modrun is a modular application composer for Tokio: domain Modules, constructor injection, explicit module boundaries, and coordinated lifecycle — wired once at the composition root. It is not a general-purpose DI container or a live runtime with inter-module messaging.

Public model (stability)

The stable surface is intentionally small:

Concept API
Register constructors provide / provide_result / provide_async / provide_result_async
Pre-built values supply
Graph roots invoke / invoke_async
Domain modules Module + Module::builder("name") + provide_private
Value groups Group<T> + provide_group* / supply_group / init_group / require_group
Lifecycle Lifecycle, Hook, task / task_with
Run / stop ModrunBuilder::run / start, RunningApp::stop, Shutdowner

Hook trait methods may only gain default implementations in minor releases. Constructor and invoker arity stays capped at eight parameters; pack extra deps in a struct.

MSRV is 1.85 (edition 2024), tracked in Cargo.toml and CI.

Application-facing API is stable since 1.0.0.

Semver (since 1.0.0)

Release Allowed changes
Patch Docs, diagnostics, tracing fields, non-breaking bug fixes
Minor New Error variants (Error is #[non_exhaustive]), new Hook methods with default impls only, new optional builder knobs that do not change existing call sites
Major Remove or rename public items, change constructor/invoker arity cap (currently eight), expose service locator or string-keyed bindings

Not covered by stability: modrun::__wiring, #[doc(hidden)] builder methods (provide_dyn, …), and any pub(crate) internals.

Performance expectations

Startup cost is dominated by your constructors (I/O, parsing), not the framework. modrun validates the graph once, constructs singletons lazily along a precomputed wave order, runs invokers, then lifecycle hooks — and drops the container. There is no runtime lookup after build.

Documented patterns that keep overhead low: Arc<T> for shared heavy deps, Arc<Group<T>> / Group<Arc<T>> for large groups, shallow modules, no dot_graph() on hot paths, and default-features = false when you do not need logging / signal. See README「Performance」/「性能」.

Application code vs wrapper authors

Application code should register plain functions:

Modrun::builder()
    .provide(new_config)
    .provide(new_server)
    .invoke(boot)

Wrapper libraries that re-export modrun wiring use the hidden __wiring module and #[doc(hidden)] builder methods. These are omitted from docs.rs navigation and are not part of the application-facing stability promise:

use modrun::__wiring::{DynProvider, InvokeFn, ProviderFn};

let provider: DynProvider = my_ctor.into_provider();
builder.provide_dyn(provider).invoke_dyn(my_boot.into_invoke());

Available in modrun::__wiring:

  • ProviderFn, FallibleProviderFn, AsyncProviderFn, FallibleAsyncProviderFn, ProviderMarker
  • InvokeFn, AsyncInvokeFn
  • DynProvider, DynInvoker

Builder: provide_dyn, invoke_dyn, provide_group_dyn (+ _private variants on Module), all #[doc(hidden)]. There are no *_mut builder methods — chain on self instead.

Deliberately rejected

We will not add APIs for:

  • Property or field injection
  • Runtime string tokens / named bindings ("primary" vs "replica") — use newtypes
  • Annotations, derives, or auto-scanning
  • Macro-generated entire containers
  • Service locator (get<T>() after build)
  • Global singleton registries
  • fx.Populate, fx.Annotate, or fx.Replace equivalents
  • First-class decorate — use wrapper constructors instead
  • Module-to-module event buses (not in scope today)

Internal Container::get exists only during build (pub(crate)); it must never become public.

Adding features

Before proposing new wiring APIs, ask:

  1. Can a plain constructor or Module scope express it already?
  2. Does it push modrun toward reflection-style DI instead of Rust constructor injection?
  3. Does it expand the method matrix (sync/async/fallible × public/private × group × dyn)?

Prefer documentation and examples over new registration verbs. See ROADMAP.md for current priorities.

Development

cargo test
cargo test --doc
cargo run --example basic

Please run tests before opening a PR. Commit messages use Conventional Commits in Chinese (项目惯例).

Releasing

Publishing is automated by .github/workflows/release.yml when a version tag is pushed.

One-time setup: add a crates.io API token as the repository secret CARGO_REGISTRY_TOKEN (Settings → Secrets and variables → Actions).

Per release:

  1. Update CHANGELOG.md (move notes from Unreleased into ## [x.y.z] - YYYY-MM-DD).

  2. Bump version in Cargo.toml.

  3. Commit and push to main.

  4. Tag and push:

    git tag v1.0.1
    git push origin v1.0.1

The workflow checks that the tag matches Cargo.toml, runs fmt/clippy/tests/doc, publishes to crates.io, and opens a GitHub Release with the matching CHANGELOG section.