mako-engine
Event-sourced process runtime for German energy market communication (MaKo).
The core runtime that all domain crates (mako-gpke, mako-wim,
mako-geli-gas, …) build on. Provides event sourcing, optimistic-concurrency
event storage, regulatory-deadline scheduling, outbox-based AS4 delivery, and
process-state projections.
Architecture
Raw EDIFACT bytes (AS4 transport)
│
▼
[edi-energy] parse · validate
│
▼ Command (typed, validated)
EngineContext::spawn / ::resume → Process::execute
│
├─ load events → reconstruct state (Workflow::apply)
├─ handle command (Workflow::handle — pure, deterministic)
└─ append EventEnvelope batch (optimistic concurrency)
EventStore ──► ProjectionRunner ──► Read models
SnapshotStore ──► Process::state_with_snapshot (O(k) replay)
OutboxStore ──► delivery worker ──► AS4 endpoint
DeadlineStore ──► scheduler ──► TimeoutDeadline command
PidRouter ──► inbound routing ──► Process
Key traits and types
| Item | Description |
|---|---|
Workflow |
Core trait — implement handle() and apply() (both must be pure / no I/O) |
Process |
Runtime handle — execute(), state(), state_with_snapshot() |
EngineContext |
Entry point — spawn() and resume() processes |
EngineBuilder |
Fluent builder for wiring stores and modules |
EventStore |
Append-only, optimistic-concurrency event log |
OutboxStore |
Transactional outbox for AS4 message delivery |
DeadlineStore |
Regulatory deadline scheduling (APERAK Fristen) |
SnapshotStore |
Optional snapshot layer for O(k) state reconstruction |
PidRouter |
Routes inbound messages to the correct workflow by Prüfidentifikator |
ProcessRegistry |
Maps conversation IDs to ProcessIdentity |
DeadLetterSink |
Receives unroutable or duplicate messages with structured reasons |
Quick start
use ;
let ctx = new
.with_event_store
.build;
// Spawn a new process for one conversation.
let process = ctx.;
let envelopes = process.execute.await?;
// Reconstruct typed state by replaying all events.
let state = process.state.await?;
// Resume on the next inbound message.
let identity = ctx.registry.lookup.await?.unwrap;
let resumed = ctx.;
Implementing a workflow
use ;
;
handle()andapply()must be pure. All parsing, validation, and I/O happens at the transport boundary before a command is constructed.
Feature flags
| Flag | Enables |
|---|---|
slatedb |
Production SlateDbEventStore / SlateDbOutboxStore — enable in binary crates only |
testing |
InMemoryEventStore, InMemoryOutboxStore, NoopDeadLetterSink — never in production |
tracing |
Structured instrumentation spans on workflow execution |
Regulatory deadlines
APERAK response deadlines are encoded via DeadlineStore. Each domain crate
uses the correct helper from fristen:
| Process family | Deadline | Helper |
|---|---|---|
| GPKE | 24 wall-clock hours | fristen::add_hours(t, 24) |
| WiM | 5 Werktage | fristen::add_werktage(d, 5, BdewMaKo) |
| GeLi Gas | 10 Werktage | fristen::add_werktage(d, 10, BdewMaKo) |
| WiM Gas | 10 Werktage | fristen::add_werktage(d, 10, BdewMaKo) |
Saturday counts as a Werktag. Sunday and public holidays do not.
Deadline arithmetic uses German local time (CET/CEST) via the time crate.
Dual-write atomicity
Events and outbox entries are written in a single WriteBatch via
AtomicAppend::append_with_outbox. Never write events first and outbox
second — a crash between the two produces a lost APERAK with no recovery path.
Format-version coexistence
WorkflowVersionPolicy::ForwardCompatible (the default for all MaKo workflows)
allows a process started under FV2025-10-01 to continue under those rules
after the FV2026-10-01 cutover. Do not use Pinned as default.
Related crates
| Crate | Role |
|---|---|
mako-engine ← this crate |
Runtime |
mako-gpke |
GPKE domain workflows |
mako-wim |
WiM Strom domain workflows |
mako-geli-gas |
GeLi Gas 3.0 domain workflows |
mako-mabis |
MABIS billing workflows |
edi-energy |
EDIFACT parse / validate (transport boundary) |
makod |
Production daemon — assembles all modules |