pub trait EngineModule: Send + 'static {
// Required method
fn name(&self) -> &'static str;
// Provided methods
fn register_pids(&self, _router: &mut PidRouter) { ... }
fn register_pids_with_roles(
&self,
router: &mut PidRouter,
_roles: &DeploymentRoles,
) { ... }
fn workflow_names(&self) -> &'static [&'static str] { ... }
fn profile_requirements(&self) -> &'static [ProfileRequirement] { ... }
fn configure(&self) -> Result<(), String> { ... }
}Expand description
A self-contained domain module that registers with the engine at startup.
Domain crates implement this trait to declare their presence in the engine.
The module name is surfaced in EngineContext::registered_modules for
diagnostics, health checks, and log output.
§Startup validation
Override configure to perform adapter coverage checks at engine startup
time. The engine calls configure for every registered module during
EngineBuilder::build and panics with an actionable message if any
module returns Err. This surfaces missing adapter registrations as a
startup failure rather than a silent runtime error.
§Example
pub struct GpkeModule;
impl EngineModule for GpkeModule {
fn name(&self) -> &'static str { "gpke" }
fn configure(&self) -> Result<(), String> {
// Validate that every known BDEW format version has an adapter:
GPKE_ADAPTER_REGISTRY
.validate_policy(&GpkeWorkflow::version_policy(), &KNOWN_FVS)
.map_err(|uncovered| format!(
"gpke: missing adapters for format versions: {:?}",
uncovered
))
}
}
let ctx = EngineBuilder::new()
.with_event_store(my_store)
.register(Box::new(GpkeModule))
.build(); // panics if GpkeModule::configure returns Err
assert_eq!(ctx.registered_modules(), &["gpke"]);Required Methods§
Provided Methods§
Sourcefn register_pids(&self, _router: &mut PidRouter)
fn register_pids(&self, _router: &mut PidRouter)
Register all PIDs this module handles into the shared PidRouter.
§Mutability contract
This method is called exactly once by EngineBuilder::build,
before the resulting EngineContext is handed to the caller. The
&mut PidRouter reference is only available here, at build time.
After build returns the router is sealed — the engine provides
only a shared &PidRouter reference, with no mutation path at runtime.
Consequence: all PIDs a module will ever need must be registered here. Do not attempt to register PIDs lazily from async handlers or after the engine has started — there is no API for that by design.
Duplicate registrations (same PID from two modules) silently overwrite
the previous mapping; the last module to register wins. Use
cargo xtask validate-pruefids to catch accidental PID conflicts
between modules before they reach production.
For role-conditional registration (PIDs that should only be active for
specific BDEW Marktrollen), override register_pids_with_roles instead.
§Example
fn register_pids(&self, router: &mut PidRouter) {
// GPKE Lieferantenwechsel / Lieferbeginn (BK6-22-024, PIDs 55001, 55002, 55017)
for &pid in &[55001_u32, 55002, 55017] {
router.register(pid, "gpke-supplier-change");
}
// Ex-MPES Einspeisestelle (übernommen per BK6-22-024 LFW24, ab 2025-06-06, PIDs 56001–56004)
for pid in 56001_u32..=56004 {
router.register(pid, "gpke-supplier-change");
}
}Sourcefn register_pids_with_roles(
&self,
router: &mut PidRouter,
_roles: &DeploymentRoles,
)
fn register_pids_with_roles( &self, router: &mut PidRouter, _roles: &DeploymentRoles, )
Register PIDs with role-context awareness.
This is the preferred override for modules that have role-conditional
PID registrations — PIDs that should only be active when this makod
instance holds a specific Marktrolle.
The default implementation calls register_pids (role-agnostic) so
existing modules that override register_pids continue to work without
changes.
Override this method instead of register_pids when any PID registration
should be conditional on the deployment role:
use mako_engine::marktrolle::Marktrolle;
fn register_pids_with_roles(&self, router: &mut PidRouter, roles: &DeploymentRoles) {
// Always register: 55001, 55002 (not role-specific)
for pid in [55001_u32, 55002] { router.register_with_module(pid, "gpke-supplier-change", self.name()); }
// Only when NB role: 19001/19002 inbound ORDRSP from MSB
if roles.contains(Marktrolle::Nb) {
for pid in [19001_u32, 19002] { router.register_with_module(pid, "gpke-konfiguration", self.name()); }
}
}§Conflict guard
Use PidRouter::register_with_module (not register) inside this
method. The conflict guard panics at build time if two modules register
the same PID to different workflows — this makes role misconfigurations
visible at startup rather than silently misrouting messages.
Sourcefn workflow_names(&self) -> &'static [&'static str]
fn workflow_names(&self) -> &'static [&'static str]
Workflow names this module handles for deadline dispatch.
Return the same name strings that register_pids maps PIDs to.
These names are stored in EngineContext::registered_workflows and
used to validate that every workflow that has deadlines scheduled is
covered by the deadline scheduler dispatch function at runtime.
The default implementation returns an empty slice. Override it to declare all workflow names that may fire deadlines:
fn workflow_names(&self) -> &'static [&'static str] {
&["gpke-supplier-change", "gpke-abrechnung"]
}Sourcefn profile_requirements(&self) -> &'static [ProfileRequirement]
fn profile_requirements(&self) -> &'static [ProfileRequirement]
Declare the EDIFACT profile types this module requires at runtime.
Returning a non-empty slice causes EngineBuilder::build to call the
registered profile validator for each requirement. If no active profile
exists for a required message type, build panics with an actionable
error so deployment fails fast rather than silently.
This replaces the previous pattern of calling
edi_energy::registry::ReleaseRegistry::global() inside configure().
Domain crates no longer need edi-energy in their production
[dependencies] — they just declare their requirements here.
fn profile_requirements(&self) -> &'static [ProfileRequirement] {
&[
ProfileRequirement { message_type: "UTILMD", label: "UTILMD Strom (GPKE)" },
ProfileRequirement { message_type: "INVOIC", label: "INVOIC Abrechnung (GPKE)" },
]
}Sourcefn configure(&self) -> Result<(), String>
fn configure(&self) -> Result<(), String>
Validate adapter coverage and configuration at engine startup.
Called by EngineBuilder::build after all modules are registered.
Return Ok(()) when the module is fully configured. Return Err(msg)
with an actionable description when an adapter or configuration is
missing — the engine will panic with that message so the deployment
fails early rather than silently.
The default implementation is a no-op (always returns Ok(())).
Override it in domain crates to call
AdapterRegistry::validate_policy and emit structured errors.
Note: if your validation needs access to the edi-energy profile
registry, use profile_requirements instead — it does not require
importing edi-energy in domain crates.
§Errors
Returns a descriptive error string when the module’s configuration is invalid.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".