Crate eventsourcing[][src]

Event Sourcing

An eventsourcing library for Rust

One of the benefits of event sourcing is that in most cases, embracing this pattern does not require that much code. However, there's still a bit of boilerplate required as well as the discipline for ensuring the events, commands, and aggregates all perform their roles without sharing concerns.

The fundamental workflow to remember is that commands are applied to aggregates, which then emit one or more events. An aggregate's business logic is also responsible for returning a new state from a previous state combined with a new event. Put mathematically, this looks like:

f(state1, event) = state2

There are some event sourcing libraries that allow for, or even encourage, mutation of state in memory. I prefer a more functional approach, and the design of this library reflects that. It encourages you to write unit tests for your aggregate business logic that are predictable and can be executed in isolation without concern for how you receive events or how you persist them in a store.

To start, you create an undecorated enum for your Command type:

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

Next, you create an enum for your events and use a derive macro to write some boilerplate on your behalf. Note how the command variants are imperative statements while the event variants are verbs phrases in the past tense. While this is by convention and not enforced via code, this is a good practice to adopt.

#[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 },
}

We then define a type that represents the state to be used by an aggregate. With that in place, we write all of our business logic, the core of our event sourcing system, in the aggregate.

#[derive(Debug)]
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,
               long,
               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 }])
   }
}

Modules

cloudevents

Cloud Events Implementation

eventstore

Event store trait and implementations

prelude

Standard prelude for eventsourcing applications

Structs

Error

An event sourcing error

Enums

Kind

Indicates the kind of event sourcing error that occurred.

Traits

Aggregate

An aggregate is where the vast majority of business logic for an event sourcing system occurs. They have two roles:

AggregateState

Aggregate state only requires that it expose the generation number. State generation can be thought of as a sequential version. When a previous state is combined with an event to produce a new state, that new state has a generation 1 higher than the previous.

Dispatcher

A dispatcher is a type of pipeline glue that eliminates a certain set of boilerplate code for when you want to emit the events produced through the application of a command immediately to a store, for a given event stream name. You don't have to build a dispatcher yourself, you can use a derive macro to make a placeholder struct your dispatcher. The result of a dispatch is a vector capturing the success of command application. If it succeeded, you will get a CloudEvent, a CloudEvents v0.1 spec-compliant data structure.

Event

All events must be serializable, and they need to expose some basic metadata about the event, including the type name, the type version, and a source to be used when events are emitted. If you use the derive macro fror events, you do not have to implement these functions manually.

Type Definitions

Result

A Result where failure is an event sourcing error