ADR-00013: Plugin dependency strategy for Lore Server modularization¶
| Field | Value |
|---|---|
| Status | accepted |
| Date | 2026-02-23 |
| Deciders | Mattias Jansson |
| Consulted | Raghav Narula, Joshua Cohen, Peter Lockhart |
Context and Problem Statement¶
The Lore Server modularization initiative aims to make lore-server flexible and decoupled from specific cloud providers
or infrastructure choices. Plugin implementations need to be integrated without tight coupling of the core server to
any third party dependencies pulled in by the plugins.
Constraints:
- Base workspace
Cargo.tomlMUST NOT be modified — Plugin crates or their dependencies can't be added as base workspace dependencies - Feature flags can't be used — Compile-time conditional compilation via Cargo features can't be used as it still makes base workspace depend on plugin dependencies
- Plugin files + implementation crates are layered in by version control — Plugins arrive together with their crate dependencies via version control overlays
build.rsdiscovers plugin files — Automatic discovery in base server of available plugins without dependencies at build time
Goal: Use the plugin system in a way that satisfies these constraints while maintaining:
- Clean separation between core server and plugins
- Easy addition of new plugins
- No modifications to base workspace configuration
Decision Drivers¶
- Zero base modification: Core workspace and
lore-serverCargo.toml remain unchanged - Compile-time safety: Invalid configurations should fail at compile time
- Version control layering: Plugins integrated via repository overlays (branches, patches, links or layers)
- Dependency isolation: Plugin dependencies don't modify the core server build
- Independent testability: Core
lore-serverbuilds and tests without any plugins - Runnable base server: The base
lore-servershould be a functional binary, not just a library
Considered Options¶
Option 1: Feature-gated optional dependencies (rejected)¶
Why rejected: Violates constraint #1 (zero base modification) and requires modifying lore-server/Cargo.toml to inject the third party dependencies.
Feature gates can still be used for optional built-in plugins shipping in the core server.
Option 2: Version-control layered plugin files (rejected)¶
Plugin .rs files and their crate dependencies are layered into lore-server via version control.
How it works:
- Base layer:
lore-server/src/plugins/contains only infrastructure (registry.rs,traits.rs) - Plugin layer (VC overlay): Adds
myplugin.rsto plugins directory - Plugin layer also adds
my-plugincrate and modifieslore-server/Cargo.toml build.rsdiscovers plugin files and generatesmod.rs
Why rejected: Violates constraint #1 (zero base modification) and requires modifying lore-server/Cargo.toml to inject the third party dependencies.
Option 3: Binary separation with server_main() entry point (chosen)¶
A cleaner architecture where:
- Base
lore-serverhas amain.rsthat callsserver_main()entry point, basic plugins without additional dependencies can be dropped inplugins/dir - Derived
lore-derived-servercrate registers custom plugins with extra dependencies before callingserver_main() - Custom plugin factory code lives in derived crate with its own
Cargo.tomldependencies
Decision Outcome¶
Chosen option: Option 3 (Binary separation with server_main() entry point)
This approach:
- Keeps
lore-serveras a functional binary — not just a library - Exposes
server_main()as a clean entry point for derived crates - Allows basic plugins to be dropped directly into
lore-server/src/plugins/ - Derived crates extend functionality without modifying base code
- Heavy dependencies stay isolated in derived crates
Consequences¶
Good:
- Base
lore-servercan run standalone with basic/no plugins - Clear separation: infrastructure in
lore-server, heavy deps in derived crates - Plugin crate versions can evolve independently
- Smoke tests in CI can test core server without plugin dependencies
- Adding new plugins requires no changes to core crates
- Developers can run a functional server immediately
Bad:
- Additional crate to maintain for production deployments in custom environments
- Two binaries exist (base and derived) — need clear naming/usage docs and deploy time selection
Neutral:
- Version control workflow changes (overlays instead of features)
- Documentation must clearly explain the two-layer architecture
Architecture¶
Repository structure¶
# BASE LAYER (always present in repository)
lore/
├── Cargo.toml # Workspace - NO plugin crates listed
├── lore-server/ # Core server (BOTH library AND binary)
│ ├── Cargo.toml # NO heavy plugin dependencies
│ ├── build.rs # Discovers plugins in plugins/ dir
│ └── src/
│ ├── lib.rs # Exports server_main(), PluginRegistry, traits
│ ├── main.rs # Calls server_main() with discovered plugins
│ └── plugins/
│ ├── mod.rs # Auto-generated (discovers basic plugins)
│ ├── registry.rs # Plugin registry infrastructure
│ ├── traits.rs # Factory trait definitions
│ └── basic.rs # Basic plugins without extra dependencies
│
├── lore-core/ # Core types and traits
├── lore-client/ # Client library
└── ... # Other core crates
# PLUGIN LAYER (added via version control overlay)
├── my-plugin/ # Custom implementation crate
│ ├── Cargo.toml # Additional third party dependencies
│ └── src/
│ └── lib.rs # Exports plugin types
└── lore-derived-server/ # Derived server binary with plugins
├── Cargo.toml # Depends on lore-server + all plugin crates (my-plugin)
└── src/
├── main.rs # Registers plugins, calls server_main()
└── plugins/
├── mod.rs # Plugin module declarations
├── myplugin.rs # Custom plugin factory wiring
Component diagram¶
flowchart TB
subgraph BASE["BASE LAYER (core repository)"]
WS["Workspace Cargo.toml<br/>members = lore-server, lore-core, lore-client, ...<br/>(no my-plugin, no lore-derived-server)"]
LS["lore-server (library + binary)<br/>lib name = lore_server, bin name = loreserver<br/>deps: lore-core only, no heavy plugin deps<br/>exports: server_main(), PluginRegistry, *PluginFactory"]
WS --> LS
end
subgraph PLUGIN["PLUGIN LAYER (version-control overlay)"]
WSX["Workspace Cargo.toml (extended)<br/>members += my-plugin, lore-derived-server"]
MP["my-plugin<br/>deps: lore-core<br/>exports: MyPlugin..."]
OP["other-plugin<br/>deps: lore-core<br/>exports: OtherPlugin..."]
DS["lore-derived-server (binary)<br/>deps: lore-server, my-plugin, other-plugin<br/>binary: loreserver"]
MAIN["lore-derived-server/src/main.rs<br/>registers plugins, then calls server_main(registry)"]
WSX --> MP
WSX --> OP
WSX --> DS
MP --> MAIN
OP --> MAIN
DS --> MAIN
end
LS -- "version control overlay" --> WSX
server_main() entry point pattern¶
flowchart TB
BM["lore-server/src/main.rs<br/>fn main(): build Registry, then server_main(reg)"]
DM["lore-derived-server/src/main.rs<br/>fn main(): build Registry,<br/>register custom plugins (myplugin, plugins::consul),<br/>register_all_plugins(), then server_main(reg)"]
LIB["lore-server/src/lib.rs<br/>pub fn server_main(registry: PluginRegistry) -> Result<()><br/>discover basic plugins, init telemetry,<br/>load config, create stores, build and run server"]
BM -- "server_main(reg)" --> LIB
DM -- "server_main(reg)" --> LIB
Build process flow¶
flowchart TB
subgraph B["1. Base layer build (CI: core tests, development)"]
B1["cargo build -p lore-server"]
B2["builds lore-server as library AND binary<br/>plugins/mod.rs generated (basic plugins only)<br/>binary loreserver works with no third-party deps<br/>all tests pass without additional dependencies"]
B1 --> B2
end
subgraph P["2. Plugin layer build (CI: integration tests, release)"]
P1["cargo build -p lore-derived-server"]
P2["builds full stack<br/>my-plugin and other-plugin compiled<br/>plugins registered before server_main()<br/>binary loreserver-derived (production)"]
P1 --> P2
end
subgraph R["3. build.rs behavior"]
R1["in lore-server: discover_modules(src/plugins/)<br/>finds registry.rs, traits.rs and any basic plugins<br/>generates mod.rs and register_all_plugins()"]
R2["in lore-derived-server: no auto-discovery<br/>plugins explicitly registered in main.rs<br/>before calling server_main()"]
end
B --> P --> R