Current section
Files
Jump to
Current section
Files
lib/persistence.ex
defmodule Workflow.Persistence do
@read_event_batch_size 100
@moduledoc """
Database side effects from Aggregates and Process Managers servers. Having them in a segregated
file helps to test, debug and share the uncommon code between them
"""
alias Workflow.Container
alias Workflow.Storage
require Logger
@typedoc "positions -> [first, last]"
@type state :: struct() # the aggregate or process manager data structure
@type events :: [struct()]
@type uuid :: String.t
@type reason :: atom
@type stream :: String.t
@doc "Rebuild if events are found, if not found, return the container state with an empty data structure"
def rebuild_from_events(%Container{} = state), do: rebuild_from_events(state, 1)
def rebuild_from_events(%Container{uuid: uuid, module: module, data: data} = state, start_version) do
case Storage.read_stream_forward(uuid, start_version, @read_event_batch_size) do
{:ok, batch} ->
batch_size = length(batch)
# rebuild the aggregate's state from the batch of events
data = apply_events(module, data, batch)
state = %Container{state |
version: start_version - 1 + batch_size,
data: data
}
case batch_size < @read_event_batch_size do
true ->
# end of event stream for aggregate so return its state
state
false ->
# fetch next batch of events to apply to updated aggregate state
rebuild_from_events(state, start_version + @read_event_batch_size)
end
{:error, _} ->
# data-structure does not exist so return empty state
state
end
state
end
def persist_events([], _aggregate_uuid, _expected_version), do: :ok
def persist_events(pending_events, uuid, expected_version) do
:ok = Storage.append_to_stream(uuid, expected_version, pending_events)
end
@doc "Receive a module that implements apply function, and rebuild the state from events"
def apply_events(module, state, events), do:
Enum.reduce(events, state, &module.apply(&2, &1))
end