GitHunt
MK

mkaszubowski/eventstore

CQRS event store using PostgreSQL for persistence

EventStore

CQRS event store implemented in Elixir. Uses PostgreSQL as the underlying storage engine.

Requires Elixir v1.5 and PostgreSQL v9.5 or newer.

EventStore supports running on a cluster of nodes.

Changelog

MIT License

Build Status


Overview


Example usage

Append events to a stream:

defmodule ExampleEvent, do: defstruct [:key]

stream_uuid = UUID.uuid4()
expected_version = 0
events = [
  %EventStore.EventData{
    event_type: "Elixir.ExampleEvent",
    data: %ExampleEvent{key: "value"},
    metadata: %{user: "someuser@example.com"},
  }
]

:ok = EventStore.append_to_stream(stream_uuid, expected_version, events)

Read all events from a single stream, starting at the stream's first event:

{:ok, events} = EventStore.read_stream_forward(stream_uuid)

More: Using the EventStore

Subscribe to events appended to all streams:

{:ok, subscription} = EventStore.subscribe_to_all_streams("example_subscription", self())

# wait for the subscription confirmation
receive do
  {:subscribed, ^subscription} ->
    IO.puts "Successfully subscribed to all streams"
end

receive_loop = fn loop ->
  # receive a batch of events appended to the event store
  receive do
    {:events, events} ->
      IO.puts "Received events: #{inspect events}"

      # ack successful receipt of events
      EventStore.ack(subscription, events)
  end

  loop.(loop)
end

# infinite receive loop
receive_loop.(receive_loop)

In production use you would use a GenServer subscriber process and the handle_info/2 callback to receive events.

More: Subscribe to streams

Used in production?

Yes, this event store is being used in production.

PostgreSQL is used for the underlying storage. Providing guarantees to store data securely. It is ACID-compliant and transactional. PostgreSQL has a proven architecture. A strong reputation for reliability, data integrity, and correctness.

Backup and administration

You can use any standard PostgreSQL tool to manage the event store data:

Benchmarking performance

Run the benchmark suite using mix with the bench environment, as configured in config/bench.exs. Logging is disabled for benchmarking.

MIX_ENV=bench mix do es.reset, app.start, bench

Example output:

## AppendEventsBench
benchmark name                         iterations   average time
append events, single writer                  100   20288.68 µs/op
append events, 10 concurrent writers           10   127416.90 µs/op
append events, 20 concurrent writers            5   376836.60 µs/op
append events, 50 concurrent writers            2   582350.50 µs/op
## ReadEventsBench
benchmark name                         iterations   average time
read events, single reader                    500   3674.93 µs/op
read events, 10 concurrent readers             50   44653.98 µs/op
read events, 20 concurrent readers             20   73927.55 µs/op
read events, 50 concurrent readers             10   188244.80 µs/op
## SubscribeToStreamBench
benchmark name                         iterations   average time
subscribe to stream, 1 subscription           100   27687.97 µs/op
subscribe to stream, 10 subscriptions          50   56047.72 µs/op
subscribe to stream, 20 subscriptions          10   194164.40 µs/op
subscribe to stream, 50 subscriptions           5   320435.40 µs/op

After running two benchmarks you can compare the runs:

MIX_ENV=bench mix bench.cmp -d percent

You can also produce an HTML page containing a graph comparing benchmark runs:

MIX_ENV=bench mix bench.graph

Contributing

Pull requests to contribute new or improved features, and extend documentation are most welcome.

Please follow the existing coding conventions, or refer to the Elixir style guide.

You should include unit tests to cover any changes.

Contributors

Need help?

Please open an issue if you encounter a problem, or need assistance.

For commercial support, and consultancy, please contact Ben Smith.

Languages

Elixir97.5%PLpgSQL2.5%
MIT License
Created February 15, 2018
Updated February 15, 2018