Command-side event sourcing kernel for MoonBit: pure command handlers, auto-wrapped EventMessage metadata (correlation/causation), optimistic concurrency with duplicate-version rejection, SQLite event store, projections and snapshots.
moon add thy1016/mooncmdimport {
"thy1016/mooncmd/aggregate",
"thy1016/mooncmd/command",
"thy1016/mooncmd/event",
"thy1016/mooncmd/store",
}let root = @aggregate.AggregateRoot::new(
"account-001",
account_handler, // (S, C) -> Result[Array[E], CommandError]
@event.EventApplier::Single(account_applier), // (S, E) -> S
AccountState::init(),
)
let root = root.exec(AccountCommand::CreateAccount("Demo", 1000))
let root = root.exec(AccountCommand::DepositMoney(500))let store = @store.SQLiteEventStore::new(
"ledger.db", account_serialize, account_deserialize, account_type_name,
)
store.append("account-001", root.uncommitted_events())
let rebuilt = @aggregate.AggregateRoot::from_events(
"account-001", store.load("account-001", 0),
account_handler, @event.EventApplier::Single(account_applier),
AccountState::init(),
)let committed = @mooncmd.dispatch(
records, // EventStore 记录面
None, // EventBus(可选)
root, // 聚合根
AccountCommand::DepositMoney(500), // 命令
projections=[live_projection], // Strong 模式需等待的投影
consistency=@mooncmd.Strong, // 默认 Eventual
)let machine : @aggregate.RetryMachine = {
default_action: fn(kind) {
match kind {
@aggregate.Duplicate => @aggregate.ActionRetry // 并发冲突:重放重执行
_ => @aggregate.ActionExhausted // 业务拒:重放同结果,不重试
}
},
decide: fn(attempt, _failures, _kind) {
if attempt > 3 {
@aggregate.GiveUpRaise
} else {
@aggregate.WaitRetry(wait_ms=100L)
}
},
}
let root = root.exec_with_effect(
AccountCommand::DepositMoney(500), machine,
@aggregate.RetryHooks::none(), fn(ms) { /* 退避等待,由调用方注入 */ },
)[aggregate.state]
fields = [{ name = "balance", type = "int", init = "0" }]
[[aggregate.events]]
name = "MoneyDeposited"
fields = [{ name = "amount", type = "int" }]
evolve = ["add balance=amount"]moon test # 生成产物结构钉 + 契约测试 + 已知答案校验| 包 | 入口 | 作用 |
|---|---|---|
| aggregate | AggregateRoot | 生命周期:new / from_events / from_snapshot / exec / execute / commit / to_snapshot |
| aggregate | exec_with_effect | Effect 重试状态机执行(RetryMachine + 策略钩子 + 等待注入) |
| command | CommandMessage / CommandHandler | 命令信封(correlation / causation / user)与纯函数处理器类型 |
| event | EventMessage / EventApplier / EventBus | 事件信封(元数据全集)/ 事件应用器 / 进程内事件外发 |
| store | SQLiteEventStore / EventStore / SnapshotStore | SQLite 后端(含 to_event_store 适配器)与记录型存储抽象 |
| projection | Projection / LiveProjection / PersistedProjection | 投影定义、内存实时投影(检查点推进)、SQLite 持久化投影 |
| monads | State / Writer / Reader / EventWriter | 纯函数 Monad 基础设施 |
| 根包 | dispatch / Router / RouteEnv / RouterBuilder | dispatch 统一编排门面(执行→落库→通知→提交→强一致等待);Router 高级编排(快照基态 / 重试机 / Reader 注入) |
moon test --target native # 全模块测试(wasm 同支持)
moon run examples/bank --target native # 全链自证,终行 EXAMPLE:PASS| 面 | 读数 |
|---|---|
| 单元与端到端测试(主包 + 各域子包:聚合生命周期、乐观并发、投影契约、Router 分发全流程) | 45 / 45 |
| 词表生成门与契约测试(examples/bank:生成产物逐字节比对、已知答案、init 值契约 + wire 往返恒等) | 18 / 18 |
| 示例全链自证(moon run examples/bank) | 终行 EXAMPLE:PASS |
pub(all) suberror EventSourcingErr {
CommandErr(CommandError)
EventStoreErr(EventStoreError)
EventBusErr(EventBusError)
SnapshotErr(SnapshotError)
AggregateErr(AggregateError)
ConsistencyErr(String)
}fn CommandConfig::with_consistency(self : CommandConfig, consistency : Consistency) -> CommandConfigtype ConsoleLoggerimpl Logger for ConsoleLoggerpub struct RouteEnv[AggregateId, S, E] {
event_store : EventStore[AggregateId, E]
snapshot_store : SnapshotStore[AggregateId, S]
event_bus : EventBus[AggregateId, E]?
logger : &Logger
default_timeout : Int64
default_retry : Int
}fn[AggregateId, E, S] RouteEnv::new(event_store : EventStore[AggregateId, E], snapshot_store : SnapshotStore[AggregateId, S], event_bus : EventBus[AggregateId, E]?, default_timeout : Int64, default_retry : Int) -> RouteEnv[AggregateId, S, E]pub struct Router[AggregateId, S, C, E] {
env : RouteEnv[AggregateId, S, E]
default_config : CommandConfig
handlers : Map[String, (S, C) -> Result[Array[E], CommandError]]
}fn[AggregateId, S, C, E] Router::dispatch(_self : Router[AggregateId, S, C, E], aggregate_id : AggregateId, cmd : CommandMessage[C], handler : (S, C) -> Result[Array[E], CommandError], applier : EventApplier[S, E], empty_state : S) -> Reader[RouteEnv[AggregateId, S, E], Result[AggregateRoot[AggregateId, S, C, E], EventSourcingErr]]fn[AggregateId, S, C, E] Router::dispatch_batch(self : Router[AggregateId, S, C, E], commands : Array[(AggregateId, CommandMessage[C])], handler : (S, C) -> Result[Array[E], CommandError], applier : EventApplier[S, E], empty_state : S) -> Reader[RouteEnv[AggregateId, S, E], Result[Array[AggregateRoot[AggregateId, S, C, E]], EventSourcingErr]]fn[AggregateId, S, C, E] Router::dispatch_default(self : Router[AggregateId, S, C, E], aggregate_id : AggregateId, cmd : CommandMessage[C], applier : EventApplier[S, E], empty_state : S) -> Result[AggregateRoot[AggregateId, S, C, E], EventSourcingErr]fn[AggregateId, S, C, E] Router::dispatch_now(self : Router[AggregateId, S, C, E], aggregate_id : AggregateId, cmd : CommandMessage[C], handler : (S, C) -> Result[Array[E], CommandError], applier : EventApplier[S, E], empty_state : S) -> Result[AggregateRoot[AggregateId, S, C, E], EventSourcingErr]fn[AggregateId, S, C, E] Router::dispatch_with_config(self : Router[AggregateId, S, C, E], aggregate_id : AggregateId, cmd : CommandMessage[C], handler : (S, C) -> Result[Array[E], CommandError], applier : EventApplier[S, E], empty_state : S, timeout_ms : Int64, max_retries : Int) -> Reader[RouteEnv[AggregateId, S, E], Result[AggregateRoot[AggregateId, S, C, E], EventSourcingErr]]fn[AggregateId, S, C, E] Router::new(env : RouteEnv[AggregateId, S, E], default_config : CommandConfig, handlers : Map[String, (S, C) -> Result[Array[E], CommandError]]) -> Router[AggregateId, S, C, E]pub(all) struct RouterBuilder[AggregateId, S, C, E] {
env : RouteEnv[AggregateId, S, E]
default_config : CommandConfig
handlers : Map[String, (S, C) -> Result[Array[E], CommandError]]
}fn[AggregateId, S, C, E] RouterBuilder::add_handler(self : RouterBuilder[AggregateId, S, C, E], command_type : String, handler : (S, C) -> Result[Array[E], CommandError]) -> RouterBuilder[AggregateId, S, C, E]fn[AggregateId, S, C, E] RouterBuilder::add_handlers(self : RouterBuilder[AggregateId, S, C, E], command_type : String, handlers : Array[(S, C) -> Result[Array[E], CommandError]]) -> RouterBuilder[AggregateId, S, C, E]fn[AggregateId, S, C, E] RouterBuilder::build(self : RouterBuilder[AggregateId, S, C, E]) -> Router[AggregateId, S, C, E]fn[AggregateId, S, C, E] RouterBuilder::handler_count(self : RouterBuilder[AggregateId, S, C, E]) -> Intfn[AggregateId, S, C, E] RouterBuilder::new(env : RouteEnv[AggregateId, S, E]) -> RouterBuilder[AggregateId, S, C, E]fn[AggregateId, S, C, E] RouterBuilder::with_consistency(self : RouterBuilder[AggregateId, S, C, E], consistency : Consistency) -> RouterBuilder[AggregateId, S, C, E]fn[AggregateId, S, C, E] RouterBuilder::with_default_retries(self : RouterBuilder[AggregateId, S, C, E], max_retries : Int) -> RouterBuilder[AggregateId, S, C, E]fn[AggregateId, S, C, E] RouterBuilder::with_default_timeout(self : RouterBuilder[AggregateId, S, C, E], timeout_ms : Int64) -> RouterBuilder[AggregateId, S, C, E]fn[Id, S, C, E] dispatch(store : EventStore[Id, E], bus : EventBus[Id, E]?, aggregate : AggregateRoot[Id, S, C, E], cmd : C, projections? : Array[LiveProjection[Id, E]], consistency? : Consistency, poll_wait? : (Int64) -> Unit, poll_budget? : Int) -> AggregateRoot[Id, S, C, E] raise EventSourcingErrInstall
Download zipCommand-side event sourcing kernel for MoonBit: pure command handlers, auto-wrapped EventMessage metadata (correlation/causation), optimistic concurrency with duplicate-version rejection, SQLite event store, projections and snapshots.