Refactor: extract shared service infrastructure (config, startup, shutdown) #123

Closed
opened 2026-03-09 23:48:41 +01:00 by shahondin1624 · 2 comments

Problem

All three implemented Rust services (audit, secrets, memory) duplicate the same structural patterns:

  1. Config loading — Each service has an identical Config::load() method:

    pub fn load(path: Option<&str>) -> anyhow::Result<Self> {
        match path {
            Some(p) => { let contents = std::fs::read_to_string(p)?; let config: Config = toml::from_str(&contents)?; Ok(config) }
            None => Ok(Self::default()),
        }
    }
    

    Files: services/audit/src/config.rs:54-63, services/secrets/src/config.rs:51-59, services/memory/src/config.rs:184-193

  2. listen_addr() method — Identical format!("{}:{}", self.host, self.port) in all three configs.

  3. shutdown_signal() function — Identical async function in all three main.rs files.

  4. Tracing initialization — Same tracing_subscriber::fmt().with_env_filter(...) boilerplate in all three main.rs files.

  5. Config environment variable pattern — Same std::env::var("SERVICE_CONFIG").ok() pattern.

Where

  • services/audit/src/config.rs, services/audit/src/main.rs
  • services/secrets/src/config.rs, services/secrets/src/main.rs
  • services/memory/src/config.rs, services/memory/src/main.rs

Impact

As more services are added (Model Gateway, Tool Broker), this duplication will grow. Bug fixes or improvements (e.g., adding SIGHUP reload, config validation, graceful drain) would need to be applied in every service.

Suggested approach

Create a services/common workspace crate (e.g., llm-multiverse-common) containing:

  • A ServiceConfig trait with load() and listen_addr() default implementations
  • A shutdown_signal() utility function
  • A init_tracing(service_name: &str) helper
  • Optionally a serve() helper that combines Server::builder + shutdown signal

Each service would depend on this crate and delegate to it, keeping service-specific config fields in their own structs.

🤖 Generated by refactor-review agent

## Problem All three implemented Rust services (audit, secrets, memory) duplicate the same structural patterns: 1. **Config loading** — Each service has an identical `Config::load()` method: ```rust pub fn load(path: Option<&str>) -> anyhow::Result<Self> { match path { Some(p) => { let contents = std::fs::read_to_string(p)?; let config: Config = toml::from_str(&contents)?; Ok(config) } None => Ok(Self::default()), } } ``` Files: `services/audit/src/config.rs:54-63`, `services/secrets/src/config.rs:51-59`, `services/memory/src/config.rs:184-193` 2. **`listen_addr()` method** — Identical `format!("{}:{}", self.host, self.port)` in all three configs. 3. **`shutdown_signal()` function** — Identical async function in all three `main.rs` files. 4. **Tracing initialization** — Same `tracing_subscriber::fmt().with_env_filter(...)` boilerplate in all three `main.rs` files. 5. **Config environment variable pattern** — Same `std::env::var("SERVICE_CONFIG").ok()` pattern. ## Where - `services/audit/src/config.rs`, `services/audit/src/main.rs` - `services/secrets/src/config.rs`, `services/secrets/src/main.rs` - `services/memory/src/config.rs`, `services/memory/src/main.rs` ## Impact As more services are added (Model Gateway, Tool Broker), this duplication will grow. Bug fixes or improvements (e.g., adding SIGHUP reload, config validation, graceful drain) would need to be applied in every service. ## Suggested approach Create a `services/common` workspace crate (e.g., `llm-multiverse-common`) containing: - A `ServiceConfig` trait with `load()` and `listen_addr()` default implementations - A `shutdown_signal()` utility function - A `init_tracing(service_name: &str)` helper - Optionally a `serve()` helper that combines Server::builder + shutdown signal Each service would depend on this crate and delegate to it, keeping service-specific config fields in their own structs. 🤖 Generated by refactor-review agent
Author
Owner

3rd review update: This issue now affects 5 Rust services (up from 3 when filed):

Pattern audit secrets memory model-gateway tool-broker
Config::load() yes yes yes yes yes
listen_addr() yes yes yes yes yes
shutdown_signal() yes yes yes yes yes
tracing init yes yes yes yes yes
audit client connect boilerplate -- yes yes yes yes
with_audit_client() builder -- yes yes yes yes

The audit client wiring pattern (connect-if-configured with warn-on-failure) is now copied identically in 4 service main.rs files. This compounds the case for a shared crate.

Priority should increase given the expanded surface area.

**3rd review update:** This issue now affects 5 Rust services (up from 3 when filed): | Pattern | audit | secrets | memory | model-gateway | tool-broker | |---------|-------|---------|--------|---------------|-------------| | `Config::load()` | yes | yes | yes | yes | yes | | `listen_addr()` | yes | yes | yes | yes | yes | | `shutdown_signal()` | yes | yes | yes | yes | yes | | tracing init | yes | yes | yes | yes | yes | | audit client connect boilerplate | -- | yes | yes | yes | yes | | `with_audit_client()` builder | -- | yes | yes | yes | yes | The audit client wiring pattern (connect-if-configured with warn-on-failure) is now copied identically in 4 service `main.rs` files. This compounds the case for a shared crate. Priority should increase given the expanded surface area.
Author
Owner

Implemented in PR #225. Created services/common (llm-common) crate with ServiceConfig trait providing default load() and listen_addr() methods, plus shared shutdown_signal(), init_tracing(), and config_path_from_env() functions. All 5 Rust services now use the shared crate, removing ~100 lines of duplicated boilerplate. Dockerfile template updated to include the new crate.

Implemented in PR #225. Created `services/common` (`llm-common`) crate with `ServiceConfig` trait providing default `load()` and `listen_addr()` methods, plus shared `shutdown_signal()`, `init_tracing()`, and `config_path_from_env()` functions. All 5 Rust services now use the shared crate, removing ~100 lines of duplicated boilerplate. Dockerfile template updated to include the new crate.
Sign in to join this conversation.