eventsourced 0.6.0

Persistent event sourcing
Documentation
# EventSourced

[![Crates.io][crates-badge]][crates-url]
[![license][license-badge]][license-url]
[![build][build-badge]][build-url]

[crates-badge]: https://img.shields.io/crates/v/eventsourced
[crates-url]: https://crates.io/crates/eventsourced
[license-badge]: https://img.shields.io/github/license/hseeberger/eventsourced
[license-url]: https://github.com/hseeberger/eventsourced/blob/main/LICENSE
[build-badge]: https://img.shields.io/github/actions/workflow/status/hseeberger/eventsourced/ci.yaml
[build-url]: https://github.com/hseeberger/eventsourced/actions/workflows/ci.yaml

Event sourced entities in [Rust](https://www.rust-lang.org/).

## Crates

- `eventsourced`: core library with `EventSourced`, `Entity`, `EvtLog`, `SnapshotStore`, etc.
- `eventsourced-nats`: [NATS]https://nats.io/ implementation for eventsourced `EvtLog` and `SnapshotStore`
- `eventsourced-postgres`: [Postgres]https://www.postgresql.org/ implementation for eventsourced`EvtLog` and `SnapshotStore`

## Concepts

EventSourced is inspired to a large degree by the excellent [Akka Persistence](https://doc.akka.io/docs/akka/current/typed/index-persistence.html) library.

The `EventSourced` trait defines types for commands, events, snapshot state and errors as well as functions for command handling, event handling and setting a snapshot state.

The `EvtLog` and `SnapshotStore` traits define a pluggable event log and a pluggable snapshot store respectively. For [NATS](https://nats.io/) and [Postgres](https://www.postgresql.org/) these are already provided.

The `Entity` struct and its associated `spawn` fuction provide for creating "running" instances of an `EventSourced` implementation, identifiable by a `Uuid`, for some event log and some snapshot store. Conversion of events and snapshot state to and from bytes happens via functions; for [prost](https://github.com/tokio-rs/prost) and [serde_json](https://github.com/serde-rs/json) these are already provided.

## Requirements for building the project

### Protobuf

Before building the project and examples, please make sure you have installed the [protobuf](https://github.com/protocolbuffers/protobuf) dependency that is not only needed for the optional byte conversion with prost, which is a default feature, but also for eventsourced-nats. The only way to get away without `protobuf` is to change the default features and not build eventsourced-nats.

On macOS `protobuf` can be installed via Homebrew:

```
brew install protobuf
```

## Counter example (no pun intended)

The `counter` package in the `example` directory contains a simple example: a counter which handles `Inc` and `Dec` commands and emits/handles `Increased` and `Decreased` events.

```rust
impl EventSourced for Counter {
    ...

    /// Command handler, returning the to be persisted events or an error.
    fn handle_cmd(&self, cmd: Self::Cmd) -> Result<Vec<Self::Evt>, Self::Error> {
        match cmd {
            Cmd::Inc(inc) => {
                // Validate command: overflow.
                if inc + self.value > u64::MAX {
                    Err(Error::Overflow {
                        value: self.value,
                        inc,
                    })
                }
                // Valid Inc command results in Increased event.
                else {
                    Ok(vec![Evt {
                        evt: Some(evt::Evt::Increased(Increased {
                            old_value: self.value,
                            inc,
                        })),
                    }])
                }
            }
            ...
        }
    }

    /// Event handler, returning whether to take a snapshot or not.
    fn handle_evt(&mut self, seq_no: u64, evt: &Self::Evt) -> Option<Self::State> {
        match evt.evt {
            Some(evt::Evt::Increased(Increased { old_value, inc })) => {
                self.value += inc;
                debug!(seq_no, old_value, inc, value = self.value, "Increased");
            }
            ...
        }

        self.snapshot_after.and_then(|snapshot_after| {
            if seq_no % snapshot_after == 0 {
                Some(self.value)
            } else {
                None
            }
        })
    }
    ...
}
```

There are also the two `counter-nats` and `counter-postgres` packages, with a binary crate each, using `eventsourced-nats` and `eventsourced-postgres` respectively for the event log.

```rust
...
let evt_log = evt_log.clone();
let snapshot_store = snapshot_store.clone();
let counter = Counter::default().with_snapshot_after(config.snapshot_after);
let counter = Entity::spawn(
    id,
    counter,
    42,
    evt_log,
    snapshot_store,
    convert::prost::binarizer(),
)
.await
.context("Cannot spawn entity")?;

tasks.spawn(async move {
    for n in 0..config.evt_count / 2 {
        if n > 0 && n % 2_500 == 0 {
            println!("{id}: {} events persisted", n * 2);
        }
        let _ = counter
            .handle_cmd(Cmd::Inc(n as u64))
            .await
            .context("Cannot handle Inc command")
            .unwrap()
            .context("Invalid command")
            .unwrap();
        let _ = counter
            .handle_cmd(Cmd::Decrease(n as u64))
            .await
            .context("Cannot handle Dec command")
            .unwrap()
            .context("Invalid command")
            .unwrap();
    }
});
...
```

Take a look at the [examples](examples) directory for more details.

### Running the counter-nats example

For the `counter-nats` example, nats-server needs to be installed. On macOS just use Homebrew:

```
brew install nats-server
```

Before running the example, start the nats-server with the `jetstream` feature enabled:

```
nats-server --jetstream
```

Then use the following command to run the example:

```
RUST_LOG=info \
    CONFIG_DIR=examples/counter-postgres/config \
    cargo run \
    --release \
    --package counter-nats
```

Notice that you can change the configuration either by changing the `defaul.yaml` file at `examples/counter-nats/config` or by overriding the configuration settings with environment variables, e.g. `APP__COUNTER__EVT_COUNT=42`:

```
RUST_LOG=info \
    APP__COUNTER__EVT_COUNT=42 \
    CONFIG_DIR=examples/counter-nats/config \
    cargo run \
    --release \
    --package counter-nats
```

### Running the counter-postgres example

For the `counter-postgres` example, PostgreSQL needs to be installed. On macOS just use Homebrew:

```
brew install postgresql@14
```

Before running the example, start PostgreSQL:

```
brew services run postgresql@14
```

Make sure you know the following connection parameters:
- host
- port
- user
- password
- dbname

Change the configuration either by changing the `defaul.yaml` file at `examples/counter-postgres/config` or by overriding the configuration settings with environment variables, e.g. `APP__EVT_LOG__DBNAME=test` or `APP__COUNTER__EVT_COUNT=42`:

Then use the following command to run the example:

```
RUST_LOG=info \
    APP__EVT_LOG__DBNAME=test \
    APP__COUNTER__EVT_COUNT=42 \
    CONFIG_DIR=examples/counter-postgres/config \
    cargo run \
    --release \
    --package counter-postgres
```

## License ##

This code is open source software licensed under the [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0.html).