fmodel-rust:使用Rust实现函数式领域建模的开源示例


当您开发信息系统来自动化业务活动时,您就是在对业务进行建模。您设计的抽象、实现的行为以及构建的 UI 交互都反映了业务 - 它们共同构成了域的模型。

这个项目可以用作库包,或作为灵感,或两者兼而有之。它提供了足够的战术领域驱动设计模式,并针对事件溯源和 CQRS 进行了优化。

抽象与概括
抽象可以隐藏不相关的细节并使用名称来引用对象。它强调对象是什么或做什么,而不是它如何表示或如何工作。
泛化通过用单个构造替换执行类似功能的多个实体来降低复杂性。
抽象和概括经常一起使用。通过参数化对抽象进行泛化,以提供更优秀的实用性。

在更高的抽象层次上,任何信息系统都负责处理意图(Command)并根据当前的State,产生新的事实(Events):

  • State/S 给定输入电流,
  • 当在输入上Command/C处理时,
  • 期望在输出中发布/发出flow新内容Events/E

新状态总是从当前状态S和当前事件演变而来E:

  • State/S 给定输入电流,
  • 当在输入上Event/E处理时,
  • 期望在输出中State/S发布新内容

两个函数包装在一个数据类型类(代数数据结构)中,该类由三个泛型参数概括:

pub struct Decider<'a, C: 'a, S: 'a, E: 'a> {
    pub decide: DecideFunction<'a, C, S, E>,
    pub evolve: EvolveFunction<'a, S, E>,
    pub initial_state: InitialStateFunction<'a, S>,
}

Decider是最重要的数据类型,但不是唯一的数据类型。


Decider
Decider 是表示主要决策算法的数据类型/结构。它属于领域层。它有三个通用参数 C、S、E,分别代表 Decider 可能包含或使用的值的类型。Decider 可以针对任何 C、S 或 E 类型进行专门化,因为这些类型不会影响其行为。例如,Decider 在 C=Int 或 C=YourCustomType 时的行为是一样的。

Decider 是一个纯域组件。

C - 命令
S - 状态
E - 事件

pub type DecideFunction<'a, C, S, E> = Box<dyn Fn(&C, &S) -> Vec<E> + 'a + Send + Sync>;
pub type EvolveFunction<'a, S, E> = Box<dyn Fn(&S, &E) -> S + 'a + Send + Sync>;
pub type InitialStateFunction<'a, S> = Box<dyn Fn() -> S + 'a + Send + Sync>;

pub struct Decider<'a, C: 'a, S: 'a, E: 'a> {
    pub decide: DecideFunction<'a, C, S, E>,
    pub evolve: EvolveFunction<'a, S, E>,
    pub initial_state: InitialStateFunction<'a, S>,
}

此外,还引入了 Decider 的 initialState,以便对 Decider 的初始状态进行更多控制。

事件源聚合
事件源聚合是使用/委托一个 Decider 来处理命令并产生新事件。它属于应用层。为了处理命令,聚合体需要通过 EventRepository.fetchEvents async 函数获取当前状态(表示为事件列表/向量),然后将命令委托给决定者,决定者可因此产生新事件。生成的事件将通过 EventRepository.save 异步函数进行存储。

这是事件源信息系统的形式化。

状态存储聚合
状态存储聚合(State-stored aggregate)是使用/委托一个 Decider 来处理命令并生成新状态。它属于应用层。为了处理命令,聚合首先需要通过 StateRepository.fetchState async 函数获取当前状态,然后将命令委托给 Decider,由 Decider 生成新状态。然后通过 StateRepository.save 异步函数存储新状态。

这是状态存储信息系统的形式化。

视图
视图是一种表示事件处理算法的数据类型,负责将事件转化为更适合查询的去规范化状态。它属于领域层。它通常用于创建 CQRS 模式的视图/查询侧。显然,CQRS 的命令侧通常是事件源聚合。

它有两个通用参数 S 和 E,分别代表 View 可能包含或使用的值的类型。View 可以为任何类型的 S、E 专门设计,因为这些类型不会影响其行为。例如,当 E=Int 或 E=YourCustomType 时,View 的行为都是一样的。

View 是一个纯域组件。

S - 状态
E - 事件

pub struct View<'a, S: 'a, E: 'a> {
    pub evolve: EvolveFunction<'a, S, E>,
    pub initial_state: InitialStateFunction<'a, S>,
}

物化视图
物化视图是使用/委托一个视图来处理 E 类型的事件,并因此保持一种非规范化的投影状态。从本质上讲,它代表了 CQRS 模式的查询/视图方面。它属于应用层。

为了处理事件,物化视图需要先通过 ViewStateRepository.fetchState 挂起函数获取当前状态,然后将事件委托给视图,视图可因此生成新状态。然后通过 ViewStateRepository.save 暂停函数存储新状态。