pub struct EngineBuilder<ES = (), SS = NoopSnapshotStore, OS = NoopOutboxStore, DS = NoopDeadlineStore, PR = NoopProcessRegistry> { /* private fields */ }Expand description
Assembles engine infrastructure and produces an EngineContext.
Uses type-state to enforce that an event store is provided before
build can be called. All other stores default to Noop
implementations.
§Quick start
// Minimal — event store only, all others are Noop:
let ctx = EngineBuilder::new()
.with_event_store(InMemoryEventStore::new())
.build();
// Full infrastructure:
let ctx = EngineBuilder::new()
.with_event_store(InMemoryEventStore::new())
.with_snapshot_store(InMemorySnapshotStore::new())
.with_outbox_store(InMemoryOutboxStore::new())
.with_deadline_store(InMemoryDeadlineStore::new())
.with_registry(InMemoryProcessRegistry::new())
.register(Box::new(GpkeModule))
.build();Implementations§
Source§impl<OS, DS, PR> EngineBuilder<(), NoopSnapshotStore, OS, DS, PR>
impl<OS, DS, PR> EngineBuilder<(), NoopSnapshotStore, OS, DS, PR>
Sourcepub fn with_stores(outbox_store: OS, deadline_store: DS, registry: PR) -> Self
pub fn with_stores(outbox_store: OS, deadline_store: DS, registry: PR) -> Self
Create a production-ready builder with explicit stores for outbox, deadline, and process registry.
This constructor is available in all build configurations including
production binaries. It enforces that the three stores that can cause
silent data loss (OutboxStore, DeadlineStore, ProcessRegistry)
are provided explicitly — there is no Noop fallback.
NoopSnapshotStore is used as the snapshot default because it is safe
for production: skipping snapshots means full replay, but no data loss.
Override with with_snapshot_store to enable snapshot-accelerated
replay.
Call with_event_store before build — the event store is
required.
let ctx = EngineBuilder::with_stores(outbox, deadline, registry)
.with_event_store(store.clone())
.with_snapshot_store(InMemorySnapshotStore::new())
.build();Source§impl<ES, SS, OS, DS, PR> EngineBuilder<ES, SS, OS, DS, PR>
impl<ES, SS, OS, DS, PR> EngineBuilder<ES, SS, OS, DS, PR>
Sourcepub fn with_event_store<ES2: EventStore>(
self,
store: ES2,
) -> EngineBuilder<ES2, SS, OS, DS, PR>
pub fn with_event_store<ES2: EventStore>( self, store: ES2, ) -> EngineBuilder<ES2, SS, OS, DS, PR>
Set the event store. Required — build() is only available once
this has been called with a type that implements EventStore.
Replaces any previously set event store (type-state transition).
Sourcepub fn with_snapshot_store<SS2: SnapshotStore>(
self,
store: SS2,
) -> EngineBuilder<ES, SS2, OS, DS, PR>
pub fn with_snapshot_store<SS2: SnapshotStore>( self, store: SS2, ) -> EngineBuilder<ES, SS2, OS, DS, PR>
Set the snapshot store (default: NoopSnapshotStore).
§Default: NoopSnapshotStore
Without calling this method the builder uses NoopSnapshotStore,
which silently discards all snapshot writes and returns None for
every snapshot read. The engine still functions correctly — every
command handling call replays the full event log from the beginning
instead of starting from a stored snapshot. For low-volume processes
this is fine; for long-lived processes with many events the replay cost
can become significant.
Enable snapshotting in production by providing a real SnapshotStore
implementation (e.g. the SlateDB-backed store in makod). In tests,
[InMemorySnapshotStore][crate::snapshot::InMemorySnapshotStore] is
available behind the testing feature flag.
Note: Process::state_with_snapshot
is a compile-time no-op when the snapshot store is NoopSnapshotStore
— it never calls the store and always returns None, so no snapshot is
ever saved or loaded.
Sourcepub fn with_outbox_store<OS2: OutboxStore>(
self,
store: OS2,
) -> EngineBuilder<ES, SS, OS2, DS, PR>
pub fn with_outbox_store<OS2: OutboxStore>( self, store: OS2, ) -> EngineBuilder<ES, SS, OS2, DS, PR>
Set the outbox store (default: NoopOutboxStore).
Sourcepub fn with_deadline_store<DS2: DeadlineStore>(
self,
store: DS2,
) -> EngineBuilder<ES, SS, OS, DS2, PR>
pub fn with_deadline_store<DS2: DeadlineStore>( self, store: DS2, ) -> EngineBuilder<ES, SS, OS, DS2, PR>
Set the deadline store (default: NoopDeadlineStore).
Sourcepub fn with_registry<PR2: ProcessRegistry>(
self,
registry: PR2,
) -> EngineBuilder<ES, SS, OS, DS, PR2>
pub fn with_registry<PR2: ProcessRegistry>( self, registry: PR2, ) -> EngineBuilder<ES, SS, OS, DS, PR2>
Set the process registry (default: NoopProcessRegistry).
Sourcepub fn with_dead_letter_sink(self, sink: impl DeadLetterSink) -> Self
pub fn with_dead_letter_sink(self, sink: impl DeadLetterSink) -> Self
Set the dead-letter sink (default: LogDeadLetterSink).
The dead-letter sink receives every message that cannot be routed to a
workflow. The default LogDeadLetterSink emits tracing::warn!
events, making rejections visible in log output without configuration.
Override with a persistent DLQ implementation in production:
use mako_engine::dead_letter::LogDeadLetterSink;
let ctx = EngineBuilder::new()
.with_event_store(my_store)
.with_dead_letter_sink(MyPersistentDlq::new())
.build();Sourcepub fn with_profile_validator(
self,
validator: impl Fn(&str) -> bool + Send + Sync + 'static,
) -> Self
pub fn with_profile_validator( self, validator: impl Fn(&str) -> bool + Send + Sync + 'static, ) -> Self
Register an edi-energy profile validator for startup profile checks.
The closure receives a message-type string (e.g. "UTILMD") and must
return true if at least one active profile for that message type is
registered for today’s date.
Wire this in makod using the edi-energy global registry:
use edi_energy::registry::ReleaseRegistry;
let today = time::OffsetDateTime::now_utc().date();
builder.with_profile_validator(move |msg_type| {
ReleaseRegistry::global()
.profiles_for_str(msg_type)
.any(|p| match (p.valid_from(), p.valid_until()) {
(Some(f), Some(u)) => f <= today && today <= u,
(Some(f), None) => f <= today,
(None, _) => true,
})
})Domain crates do not need to call this — they only declare
profile_requirements.
Sourcepub fn register(self, module: Box<dyn EngineModule>) -> Self
pub fn register(self, module: Box<dyn EngineModule>) -> Self
Register a domain module.
The module name becomes visible in
EngineContext::registered_modules after build is called.
Sourcepub fn with_deployment_roles(self, roles: DeploymentRoles) -> Self
pub fn with_deployment_roles(self, roles: DeploymentRoles) -> Self
Set the active DeploymentRoles for this engine instance.
Controls role-conditional PID registration in EngineModule::register_pids_with_roles.
The default is DeploymentRoles::all(), which registers every PID unconditionally
— identical to the pre-role-aware behavior. Providing an explicit role set
restricts role-conditional blocks to only the declared roles:
- NB-only (
DeploymentRoles::nb()): 19001/19002 route togpke-konfiguration; WiM nMSB blocks are skipped. - nMSB-only (
DeploymentRoles::nmsb()): 19001/19002 route towim-geraeteubernahme; GPKE NB blocks are skipped. - NB + gMSB (
DeploymentRoles::nb_msb()): most common Stadtwerke combination.
§Conflict guard
When two modules would register the same PID to different workflows, the
engine panics during build. Set explicit roles to prevent both modules from
activating the same PID simultaneously:
use mako_engine::marktrolle::DeploymentRoles;
let ctx = EngineBuilder::with_stores(outbox, deadline, registry)
.with_event_store(store)
.with_deployment_roles(DeploymentRoles::nb()) // only NB: GPKE gets 19001/19002
.register(Box::new(GpkeModule))
.register(Box::new(WimModule)) // nMSB block skipped — no conflict
.build();Source§impl<ES, SS, OS, DS, PR> EngineBuilder<ES, SS, OS, DS, PR>
impl<ES, SS, OS, DS, PR> EngineBuilder<ES, SS, OS, DS, PR>
Sourcepub fn build(self) -> EngineContext<ES, SS, OS, DS, PR>
pub fn build(self) -> EngineContext<ES, SS, OS, DS, PR>
Build the EngineContext.
Consumes the builder. All registered modules and configured stores are
moved into the returned EngineContext.
This method is only available when ES implements EventStore.
If you have not called with_event_store, this will not compile.
§Panics
Panics when any registered module returns Err from
EngineModule::configure. The panic message includes the module
name and the error string so the deployment failure is actionable.