Skip to main content

EngineModule

Trait EngineModule 

Source
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§

Source

fn name(&self) -> &'static str

Stable, unique name for this domain module.

Used in diagnostics, health checks, and structured log output. Choose a short lowercase identifier (e.g. "gpke", "wim", "geli").

Provided Methods§

Source

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");
    }
}
Source

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.

Source

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"]
}
Source

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)" },
    ]
}
Source

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".

Implementors§