Sign in

    mooncmd

    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.

    event-sourcing
    cqrs
    event-store
    aggregate
    snapshot
    sqlite
    Download zip
    Author
    Version
    0.1.1
    License
    MulanPSL-2.0
    Last updated
    2 hours ago
    Downloads
    4

    #mooncmd

    thy1016/mooncmd 是一个用 MoonBit 编写的命令侧事件溯源(Event Sourcing / CQRS)框架库:命令处理器产出纯事件,框架统一包装事件元数据(事件 ID、 correlation / causation 追溯链、时间戳、schema 版本),Router 作为唯一持久化 写入口;内置乐观并发守门、SQLite 事件存储、拉取式投影、快照加速、Effect 重试状态机,以及聚合词表(TOML)代码生成。

    #特性

    • 纯事件主链:命令处理器为纯函数 (S, C) -> Result[Array[E], CommandError],只做业务裁决、不碰元数据与 存储;AggregateRoot 承担生命周期(执行 / 提交 / 重放重建 / 快照)。
    • 元数据自动包装:EventMessage 由框架独占组装——event_id(UUID v4)、 correlation_id 承自命令、causation_id 记前因、user_id 全链透传、 schema_version 预留位、时间戳唯一取自 @async.now() 注入点。
    • 乐观并发:批次首事件版本必须等于当前版本 + 1,否则 DuplicateVersion 拒写;表级 UNIQUE(aggregate_id, version) 竞态兜底; 事务失败整体回滚。
    • 快照加速:to_snapshot / SnapshotStore 记录面 + from_snapshot 基态重建(快照读路径已接入分发主流程);快照态起跑 + 尾部重放与全量 重放等价有契约钉。
    • 拉取式投影:Projection 记录面 + LiveProjection(订阅位置 Origin / Current / FromVersion,检查点即版本)+ SQLite 持久化投影。
    • Effect 重试状态机:RetryMachine 协议面(错误分类封闭集 + 纯裁决, 无时钟无副作用)+ exec_with_effect(固定 / 指数退避、按错误类别 分派、熔断;退避等待由调用方注入)。
    • 聚合词表代码生成:TOML 声明(约 30–50 行)自动产出状态结构、事件与 命令枚举、apply 演化链、命令分派骨架、wire 序列化与契约测试门; 业务裁决归手写策略钩子。
    • Monad 注入面:monads 子包提供 State / Writer / Reader / EventWriter 纯函数基础设施,不含事件溯源语义。

    #安装

    确保环境中已安装 MoonBit 工具链,然后在模块根目录:

    moon add thy1016/mooncmd

    (模块源码正式地址:gitlink https://gitlink.org.cn/thy7/mbt.cmd 与 GitHub 镜像 https://github.com/72-j/mbt.cmd;工作区多模块场景可直接以 本地树路径引用。)

    框架按域分包,在用到它的包的 moon.pkg import 块中按需添加:

    import {
    "thy1016/mooncmd/aggregate",
    "thy1016/mooncmd/command",
    "thy1016/mooncmd/event",
    "thy1016/mooncmd/store",
    }

    #快速上手

    #定义聚合并执行命令

    命令处理器与事件应用器均为纯函数;exec 自动包装 CommandMessage 并链式返回:

    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))

    #SQLite 落库与重放重建

    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(),
    )

    #统一分发(dispatch 门面)

    执行、落库、通知、提交、强一致等待,一个调用串完:

    let committed = @mooncmd.dispatch(
    records, // EventStore 记录面
    None, // EventBus(可选)
    root, // 聚合根
    AccountCommand::DepositMoney(500), // 命令
    projections=[live_projection], // Strong 模式需等待的投影
    consistency=@mooncmd.Strong, // 默认 Eventual
    )

    #Effect 重试(乐观并发自动重试)

    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) { /* 退避等待,由调用方注入 */ }, )

    #聚合词表代码生成

    用 TOML 声明聚合形状(字段 / 事件 / 命令 / 机械演化指令),生成器产出 其余全部样板;模板见 examples/bank/account_agg.toml:

    [aggregate.state] fields = [{ name = "balance", type = "int", init = "0" }] [[aggregate.events]] name = "MoneyDeposited" fields = [{ name = "amount", type = "int" }] evolve = ["add balance=amount"]

    校验(自足,克隆即可跑):

    moon test # 生成产物结构钉 + 契约测试 + 已知答案校验

    词表再生成属维护侧工具链(骨架生成器独立仓,不随本模块发布):改动 account_agg.toml 等词表后由生成器刷新产物,生成物禁手写。

    生成物包含:状态结构与初值、事件 / 命令枚举、apply 演化链、命令分派 骨架、wire 序列化(含 schema 版本预留位)、契约测试门(init 值契约 + wire 往返恒等)。事件命名与粒度属通用语言决策,由词表声明;机械演化 与序列化由生成器承担。完整上手路径见 examples/bank(可运行全链自证: moon run examples/bank --target native)。

    #API 总览

    包入口作用
    aggregateAggregateRoot生命周期:new / from_events / from_snapshot / exec / execute / commit / to_snapshot
    aggregateexec_with_effectEffect 重试状态机执行(RetryMachine + 策略钩子 + 等待注入)
    commandCommandMessage / CommandHandler命令信封(correlation / causation / user)与纯函数处理器类型
    eventEventMessage / EventApplier / EventBus事件信封(元数据全集)/ 事件应用器 / 进程内事件外发
    storeSQLiteEventStore / EventStore / SnapshotStoreSQLite 后端(含 to_event_store 适配器)与记录型存储抽象
    projectionProjection / LiveProjection / PersistedProjection投影定义、内存实时投影(检查点推进)、SQLite 持久化投影
    monadsState / Writer / Reader / EventWriter纯函数 Monad 基础设施
    根包dispatch / Router / RouteEnv / RouterBuilderdispatch 统一编排门面(执行→落库→通知→提交→强一致等待);Router 高级编排(快照基态 / 重试机 / Reader 注入)

    #错误面

    总错误 EventSourcingErr 六臂:CommandErr / EventStoreErr / SnapshotErr / EventBusErr / AggregateErr / ConsistencyErr (Strong 一致性轮询超预算)。常用变体:CommandError 六变体 (InvalidCommand / Unauthorized / PreconditionFailed / NotFound / Conflict / UnregisteredCommand);EventStoreError 三变体 (DuplicateVersion / StorageError / SerializationError)。均实现 Show,可按变体 match 处理。

    #设计边界

    • 命令处理器只返回纯事件;事件元数据由 AggregateRoot 独占包装; 持久化只经 Router 触发。
    • 事件时间戳只来自 @async.now() 注入点;投影与确定性路径不读取 当前时钟。
    • 不做分布式 / 跨进程事件总线(EventBus 仅进程内可选挂点);不做 事件升级 / 迁移框架(schema_version 只预留);不承诺第二存储后端; 不内置业务聚合(examples/bank 为示例与测试模板)。

    定位与边界的完整说明见 spec.md;设计裁定沿革见 adr.md。

    #测试与验证

    moon test --target native # 全模块测试(wasm 同支持) moon run examples/bank --target native # 全链自证,终行 EXAMPLE:PASS

    运行时输出各测试面的计数行(下表实测于 2026-10-24;分包读数,合计 63 / 63):

    面读数
    单元与端到端测试(主包 + 各域子包:聚合生命周期、乐观并发、投影契约、Router 分发全流程)45 / 45
    词表生成门与契约测试(examples/bank:生成产物逐字节比对、已知答案、init 值契约 + wire 往返恒等)18 / 18
    示例全链自证(moon run examples/bank)终行 EXAMPLE:PASS

    版本沿革见 CHANGELOG.md。

    #许可证

    本项目采用木兰宽松许可证 v2(Mulan PSL v2,见 LICENSE)。 官方文本:http://license.coscl.org.cn/MulanPSL2。

    Logger

    pub trait Logger {
    fn info(Self, String) -> Unit
    }

    CommandConfig

    pub(all) struct CommandConfig {
    timeout_ms : Int64
    max_retries : Int
    consistency : Consistency
    }

    CommandConfig::new

    CommandConfig::with_consistency

    fn CommandConfig::with_consistency(self : CommandConfig, consistency : Consistency) -> CommandConfig

    CommandConfig::with_retries

    fn CommandConfig::with_retries(self : CommandConfig, max_retries : Int) -> CommandConfig

    CommandConfig::with_timeout

    fn CommandConfig::with_timeout(self : CommandConfig, timeout_ms : Int64) -> CommandConfig

    Consistency

    pub(all) enum Consistency {
    Eventual
    Strong
    }

    ConsoleLogger

    type ConsoleLogger

    ConsoleLogger::info

    fn ConsoleLogger::info(_self : ConsoleLogger, msg : String) -> Unit

    R-05 收口(2026-10-08):显式提升声明,消 implicit_impl_as_method([0079])

    MuteLogger

    type MuteLogger

    MuteLogger::info

    fn MuteLogger::info(_self : MuteLogger, _msg : String) -> Unit

    RouteEnv

    pub 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
    }

    路由环境(依赖注入容器)

    RouteEnv::new

    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]

    Router

    pub struct Router[AggregateId, S, C, E] {
    env : RouteEnv[AggregateId, S, E]
    default_config : CommandConfig
    handlers : Map[String, (S, C) -> Result[Array[E],
    CommandError
    ]]
    }

    路由器(类似 Commanded Router)

    Router::dispatch

    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]]

    Router::dispatch_batch

    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]]

    所有命令要么全部成功,要么全部失败

    Router::dispatch_default

    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]

    如果只注册了一个处理器,可以直接使用此方法

    Router::dispatch_now

    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]

    执行命令(直接执行,不返回 Reader)

    Router::dispatch_with_config

    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]]

    带配置的命令分发(重试裁决走 Effect 协议): max_retries 生效 = Duplicate 族乐观并发重试(重载→重执行→重追加, ES 正统重试语义——命令对最新态重新裁决),裁决走 RetryMachine 记录面 (effect_kind_of_sourcing_err 分类 + 机器 decide 上限门——分类/动作/上限 全机器面,与 aggregate/effect 协议同源);timeout_ms 降级声明 = MVP 同步径无定时器,退避时长同步径忽略(WaitRetry → 立即重试),随 async dispatch 面另役。dispatch_with_config 与 dispatch 的差别仅此。

    Router::new

    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]

    RouterBuilder

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

    RouterBuilder::add_handler

    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]

    RouterBuilder::add_handlers

    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]

    批量添加处理器

    RouterBuilder::build

    fn[AggregateId, S, C, E] RouterBuilder::build(self : RouterBuilder[AggregateId, S, C, E]) -> Router[AggregateId, S, C, E]

    RouterBuilder::handler_count

    fn[AggregateId, S, C, E] RouterBuilder::handler_count(self : RouterBuilder[AggregateId, S, C, E]) -> Int

    获取已注册的处理器数量

    RouterBuilder::new

    fn[AggregateId, S, C, E] RouterBuilder::new(env : RouteEnv[AggregateId, S, E]) -> RouterBuilder[AggregateId, S, C, E]

    RouterBuilder::with_consistency

    fn[AggregateId, S, C, E] RouterBuilder::with_consistency(self : RouterBuilder[AggregateId, S, C, E], consistency : Consistency) -> RouterBuilder[AggregateId, S, C, E]

    设置一致性级别

    RouterBuilder::with_default_retries

    fn[AggregateId, S, C, E] RouterBuilder::with_default_retries(self : RouterBuilder[AggregateId, S, C, E], max_retries : Int) -> RouterBuilder[AggregateId, S, C, E]

    设置默认重试次数

    RouterBuilder::with_default_timeout

    fn[AggregateId, S, C, E] RouterBuilder::with_default_timeout(self : RouterBuilder[AggregateId, S, C, E], timeout_ms : Int64) -> RouterBuilder[AggregateId, S, C, E]

    设置默认超时时间(毫秒)

    dispatch

    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 EventSourcingErr

    Dispatch 统一编排门面。 projections = Strong 模式下需等待的投影注册表(Eventual 可空); poll_wait = Strong 轮询间的等待注入(测试用假钟 / 生产接 async sleep); poll_budget = 轮询次数上限(超过即 ConsistencyErr)。

    dispatch_ask

    fn[AggregateId, S, E] dispatch_ask() ->
    Reader
    [RouteEnv[AggregateId, S, E], RouteEnv[AggregateId, S, E]]

    获取环境

    dispatch_asks

    fn[AggregateId, S, E, A] dispatch_asks(f : (RouteEnv[AggregateId, S, E]) -> A) ->
    Reader
    [RouteEnv[AggregateId, S, E], A]

    从环境中提取值

    dispatch_reader_bind

    fn[AggregateId, S, E, R, A] dispatch_reader_bind(reader :
    Reader
    [RouteEnv[AggregateId, S, E], R], f : (R) ->
    Reader
    [RouteEnv[AggregateId, S, E], A]) ->
    Reader
    [RouteEnv[AggregateId, S, E], A]

    绑定 DispatchReader

    dispatch_reader_map

    fn[AggregateId, S, E, R, A] dispatch_reader_map(reader :
    Reader
    [RouteEnv[AggregateId, S, E], R], f : (R) -> A) ->
    Reader
    [RouteEnv[AggregateId, S, E], A]

    映射 DispatchReader 的结果

    dispatch_reader_new

    fn[AggregateId, S, E, R] dispatch_reader_new(f : (RouteEnv[AggregateId, S, E]) -> R) ->
    Reader
    [RouteEnv[AggregateId, S, E], R]

    创建一个 DispatchReader

    dispatch_reader_run

    fn[AggregateId, S, E, R] dispatch_reader_run(reader :
    Reader
    [RouteEnv[AggregateId, S, E], R], env : RouteEnv[AggregateId, S, E]) -> R

    运行 DispatchReader

    effect_kind_of_sourcing_err

    总错误分类(Effect 协议结构知识面——分类固定非裁决): 命令/存储族各归各分类器;快照故障归 Storage;聚合 id 解析形归 Other。

    log_warn

    fn log_warn(msg : String) -> Unit

    make_console_logger

    fn make_console_logger() -> ConsoleLogger

    make_mute_logger

    fn make_mute_logger() -> MuteLogger