6个版本

0.1.5 2020年3月11日
0.1.4 2020年2月20日
0.1.1 2018年5月23日
0.1.0 2018年4月25日

#1052 in 异步

Download history 161/week @ 2024-04-02 102/week @ 2024-04-09 134/week @ 2024-04-16 83/week @ 2024-04-23 113/week @ 2024-04-30 110/week @ 2024-05-07 81/week @ 2024-05-14 58/week @ 2024-05-21 105/week @ 2024-05-28 140/week @ 2024-06-04 150/week @ 2024-06-11 81/week @ 2024-06-18 151/week @ 2024-06-25 37/week @ 2024-07-02 135/week @ 2024-07-09 144/week @ 2024-07-16

每月494次下载
用于 6 个crate(直接使用2个)

Apache-2.0

23KB
272

事件源

Rust的事件源库

事件源的一个好处是,在大多数情况下,采用这种模式并不需要太多的代码。然而,仍然需要一些模板代码以及确保事件、命令和聚合体各自发挥其作用而不共享关注点的纪律。

要记住的基本工作流程是:将 命令 应用到 聚合体,然后聚合体发出一个或多个事件。聚合体的业务逻辑还负责从一个先前的状态和一个新事件结合返回一个新的状态。用数学公式表示,这看起来像是

f(state1, event) = state2

有些事件源库允许或在某种程度上鼓励在内存中对状态进行修改。我更喜欢一种更函数式的方法,这个库的设计也反映了这一点。它鼓励你为聚合体的业务逻辑编写可预测的单元测试,这些测试可以在隔离的情况下执行,而不必担心你如何接收事件或如何将它们持久化存储。

首先,你为你的 Command 类型创建一个未装饰的枚举

enum LocationCommand {
   UpdateLocation { lat: f32, long: f32, alt: f32 },
}

接下来,你为事件创建一个枚举,并使用 derive 宏为你编写一些模板代码。注意命令变体是 命令式语句,而事件变体是 动词短语,用过去时态。虽然这仅是一种约定,并不通过代码强制执行,但这是一种值得采用的良好实践。

#[derive(Serialize, Deserialize, Debug, Clone, Event)]
#[event_type_version("1.0")]
#[event_source("events://github.com/pholactery/eventsourcing/samples/location")]
enum LocationEvent {
   LocationUpdated { lat: f32, long: f32, alt: f32 },
}

然后,我们定义一个类型,代表聚合体将要使用的状态。有了这个,我们就把所有的业务逻辑,也就是我们事件源系统的核心,写在聚合体中。

#[derive(Debug, Clone)]
struct LocationData {
    lat: f32,
    long: f32,
    alt: f32,
    generation: u64,
}

impl AggregateState for LocationData {
    fn generation(&self) -> u64 {
        self.generation
    }
}

struct Location;
impl Aggregate for Location {
   type Event = LocationEvent;
   type Command = LocationCommand;
   type State = LocationData;

   fn apply_event(state: &Self::State, evt: &Self::Event) -> Result<Self::State> {
       // TODO: validate event
       let ld = match evt {
           LocationEvent::LocationUpdated { lat, long, alt } => LocationData {
               lat: *lat,
               long: *long,
               alt: *alt,
               generation: state.generation + 1,
           },
       };
       Ok(ld)
   }

   fn handle_command(_state: &Self::State, cmd: &Self::Command) -> Result<Vec<Self::Event>> {
       // TODO: add code to validate state and command

       // if validation passes...
       Ok(vec![LocationEvent::LocationUpdated { lat: 10.0, long: 10.0, alt: 10.0 }])
   }
}

有关更多用法示例,请查看github上的 examples 目录。

依赖项

~1.6–6.5MB
~127K SLoC