Skip to content

Commit

Permalink
feat: event metadata (#8)
Browse files Browse the repository at this point in the history
* wip

* feat: event metadata

* metadata need not be Deserialize
  • Loading branch information
hseeberger authored May 29, 2024
1 parent 023bb60 commit 65d552f
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 46 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ trait-variant = { version = "0.1" }
sqlx = { version = "0.7", features = [ "uuid" ] }
testcontainers = { version = "0.17" }
testcontainers-modules = { version = "0.5", features = [ "postgres" ] }
time = { version = "0.3", features = [ "serde-human-readable" ] }
tokio = { version = "1", features = [ "macros" ] }
uuid = { version = "1.8", features = [ "serde", "v7" ] }
48 changes: 41 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
[license-badge]: https://img.shields.io/github/license/hseeberger/evented
[license-url]: https://github.com/hseeberger/evented/blob/main/LICENSE

evented is a library for [Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html) where state changes are persisted as events. It originated from [eventsourced](https://github.com/hseeberger/eventsourced), but offers additional strong consistency features by tightly coupling to [PostgreSQL](https://www.postgresql.org/).
evented is a library for [Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html) where state changes are persisted as events. It originated from [eventsourced](https://github.com/hseeberger/eventsourced), but offers additional strong consistency features by tightly coupling to [PostgreSQL](https://www.postgresql.org/) as well as other features like event metadata.

The core abstraction of evented is an `EventSourcedEntity` which can be identified via an ID: an `Entity` implementation defines its state and event handling and associated `Command` implementations define its command handling.

When an event-sourced entity receives a command, the respective command handler is called, which either returns a sequence of to be persisted events or a rejection. If events are returned, these are transactionally persisted, thereby also invoking an optional `EventListener`. Concurrency is handled by optimistic locking via per event sequence numbers.
When an event-sourced entity receives a command, the respective command handler is called, which either returns a sequence of to be persisted events plus metadata or a rejection. If events are returned, these are transactionally persisted, thereby also invoking an optional `EventListener`. Concurrency is handled by optimistic locking via per event sequence numbers.

When creating an event-sourced entity, its events are loaded and its state is conctructed by applying them to its event handler. This state is then used by the command handlers to decide whether a command should be accepted – resulting in events to be persisted and applied – or rejected.

Expand All @@ -25,6 +25,7 @@ pub struct Counter(u64);
impl Entity for Counter {
type Id = Uuid;
type Event = Event;
type Metadata = Metadata;

const TYPE_NAME: &'static str = "counter";

Expand Down Expand Up @@ -53,12 +54,26 @@ impl Command for Increase {
self,
id: &<Self::Entity as Entity>::Id,
entity: &Self::Entity,
) -> Result<Vec<<Self::Entity as Entity>::Event>, Self::Rejection> {
) -> Result<
Vec<
impl Into<
EventWithMetadata<
<Self::Entity as Entity>::Event,
<Self::Entity as Entity>::Metadata,
>,
>,
>,
Self::Rejection,
> {
let Increase(inc) = self;
if entity.0 > u64::MAX - inc {
Err(Overflow)
} else {
Ok(vec![Event::Increased { id: *id, inc }])
let increased = Event::Increased { id: *id, inc };
let metadata = Metadata {
timestamp: OffsetDateTime::now_utc(),
};
Ok(vec![increased.with_metadata(metadata)])
}
}
}
Expand All @@ -77,19 +92,38 @@ impl Command for Decrease {
self,
id: &<Self::Entity as Entity>::Id,
entity: &Self::Entity,
) -> Result<Vec<<Self::Entity as Entity>::Event>, Self::Rejection> {
) -> Result<
Vec<
impl Into<
EventWithMetadata<
<Self::Entity as Entity>::Event,
<Self::Entity as Entity>::Metadata,
>,
>,
>,
Self::Rejection,
> {
let Decrease(dec) = self;
if entity.0 < dec {
Err(Underflow)
Err::<Vec<_>, Underflow>(Underflow)
} else {
Ok(vec![Event::Decreased { id: *id, dec }])
let decreased = Event::Decreased { id: *id, dec };
let metadata = Metadata {
timestamp: OffsetDateTime::now_utc(),
};
Ok(vec![decreased.with_metadata(metadata)])
}
}
}

#[derive(Debug, PartialEq, Eq)]
pub struct Underflow;

#[derive(Debug, Serialize, Deserialize)]
pub struct Metadata {
timestamp: OffsetDateTime,
}

struct Listener;

impl EventListener<Event> for Listener {
Expand Down
8 changes: 0 additions & 8 deletions sql/create_event_log_text.sql

This file was deleted.

1 change: 1 addition & 0 deletions sql/create_event_log_uuid.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ CREATE TABLE
seq_no bigint,
type text,
event bytea,
metadata jsonb,
PRIMARY KEY (id, seq_no)
);
Loading

0 comments on commit 65d552f

Please sign in to comment.