Skip to content
an event store + cqrs
Elixir
Branch: master
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
config ecto 3 and deps Mar 7, 2019
lib Removed unused dependency Jul 11, 2019
priv/repo
test ecto 3 and deps Mar 7, 2019
.formatter.exs sensible specs Apr 26, 2019
.gitignore the road to 0.1 Apr 4, 2018
.travis.yml travis says we can't have nice things Sep 14, 2018
LICENSE the road to 0.1 Apr 4, 2018
README.md README revelation: remove redundancy Jun 25, 2018
coveralls.json drop pid from new Jun 21, 2018
dialyzer.ignore-warnings mix task for generating migrations Apr 12, 2018
mix.exs deploy fix for migration task Jul 11, 2019
mix.lock prepare for ecto 3 release Apr 21, 2019

README.md

Maestro

Maestro is an event sourcing library. It is inspired by CQRS and re-uses terminology where appropriate. The divergence from being a CQRS framework is intentional as Maestro focuses on processing commands in a consistent manner and replaying events in a consistent order.

Currently, the only storage adapter suited to a multi-node environment is the Maestro.Store.Postgres adapter. The Maestro.Store.InMemory adapter exists for testing purposes only.

Status

Hex Build Status Coverage

Documentation is available here.

Installation

def deps do
  [{:maestro, "~> 0.2"}]
end

Database Configuration

Maestro is intended to be used alongside an existing database/ecto repo.

config :maestro,
  storage_adapter: Maestro.Store.Postgres,
  repo: MyApp.Repo

To generate the migrations for the snapshot and event logs, do:

mix maestro.create.event_store_migration

Example

There are three behaviours that make the command/event lifecycle flow: Maestro.Aggregate.CommandHandler, Maestro.Aggregate.EventHandler, and Maestro.Aggregate.ProjectionHandler. Modules implementing the command and event handler behaviours are looked up via a configurable :command_prefix and :event_prefix respectively. Projections are reserved for maintaining other models/representations within the event's transaction.

defmodule MyApp.Aggregate do
  use Maestro.Aggregate.Root,
    command_prefix: MyApp.Aggregate.Commands,
    event_prefix: MyApp.Aggregate.Events

  def initial_state, do: %{"value" => 0}

  def prepare_snapshot(state), do: state

  def use_snapshot(_curr, %Maestro.Types.Snapshot{body: state}), do: state
end

defmodule MyApp.Aggregate.Commands.IncrementCounter do

  @behaviour Maestro.Aggregate.CommandHandler

  alias Maestro.Types.Event

  def eval(aggregate, _command) do
    [
      %Event{
        aggregate_id: aggregate.id,
        type: "counter_incremented",
        body: %{}
      }
    ]
  end
end

defmodule MyApp.Aggregate.Events.CounterIncremented do

  @behaviour Maestro.Aggregate.EventHandler

  def apply(state, _event), do: Map.update!(state, "value", &(&1 + 1))
end
iex(1)> {:ok, id} = MyApp.Aggregate.new()
iex(2)> :ok = MyApp.Aggregate.evaluate(%Maestro.Types.Command{aggregate_id: id, type: "increment_counter", data: %{}})
iex(3)> {:ok, %{"value" => 1}} = MyApp.Aggregate.get(id)
You can’t perform that action at this time.