Packages
eventstore
0.0.4
1.4.8
1.4.7
1.4.6
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.3
1.2.2
1.2.1
1.2.0
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-rc.0
0.17.0
0.16.2
0.16.1
0.16.0
0.15.1
0.15.0
0.14.0
0.14.0-rc.0
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.0
0.11.0-rc.0
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.1
0.2.0
0.1.0
0.0.5
0.0.4
0.0.3
0.0.2
0.0.1
Event store using PostgreSQL for persistence.
Current section
Files
Jump to
Current section
Files
lib/eventstore/storage/appender.ex
defmodule EventStore.Storage.Appender do
require Logger
alias EventStore.EventData
alias EventStore.Sql.Statements
alias EventStore.Storage.Appender
def append(conn, stream_id, expected_version, events) do
case Appender.Query.latest_version(conn, stream_id) do
{:ok, ^expected_version} -> execute(conn, stream_id, expected_version, events)
{:ok, latest_version} -> wrong_expected_version(stream_id, expected_version, latest_version)
{:error, reason} -> failed_to_append(stream_id, reason)
end
end
defp wrong_expected_version(stream_id, expected_version, latest_version) do
Logger.warn "failed to append events to stream id #{stream_id}, expected version #{expected_version} but latest version is #{latest_version}"
{:error, :wrong_expected_version}
end
defp failed_to_append(stream_id, reason) do
Logger.warn "failed to append events to stream id #{stream_id} due to #{reason}"
{:error, reason}
end
defp execute(conn, stream_id, expected_version, events) do
conn
|> Postgrex.transaction(&execute_within_transaction(&1, stream_id, expected_version, events))
|> handle_response(stream_id)
end
defp execute_within_transaction(transaction, stream_id, expected_version, events) do
{:ok, query} = prepare_query(transaction)
initial_stream_version = expected_version + 1
persisted_events =
events
|> Enum.map(&assign_stream_id(&1, stream_id))
|> Enum.with_index(initial_stream_version)
|> Enum.map(&assign_stream_version/1)
|> Enum.map(&assign_event_type/1)
|> Enum.map(&encode_headers/1)
|> Enum.map(&encode_payload/1)
|> Enum.reduce_while({:ok, []}, fn(event, {:ok, events}) ->
case append_event(transaction, query, event) do
{:ok, _} -> {:cont, {:ok, [event | events]}}
{:error, %Postgrex.Error{postgres: %{message: reason}}} -> {:halt, {:error, reason}}
response -> {:halt, response}
end
end)
case persisted_events do
{:ok, events} -> length(events)
{:error, reason} -> Postgrex.rollback(transaction, reason)
end
end
defp assign_stream_id(%EventData{} = event, stream_id) do
%EventData{event | stream_id: stream_id}
end
defp assign_stream_version({%EventData{} = event, stream_version}) do
%EventData{event | stream_version: stream_version}
end
defp assign_event_type(%EventData{payload: payload} = event) do
event_type = payload.__struct__ |> Atom.to_string
%EventData{event | event_type: event_type}
end
defp encode_headers(%EventData{headers: headers} = event) do
%EventData{event | headers: Poison.encode!(headers)}
end
defp encode_payload(%EventData{payload: payload} = event) do
%EventData{event | payload: Poison.encode!(payload)}
end
defp prepare_query(transaction) do
Postgrex.prepare(transaction, "create_event", Statements.create_event)
end
defp append_event(transaction, query, %EventData{} = event) do
Postgrex.execute(transaction, query, [
event.stream_id,
event.stream_version,
event.correlation_id,
event.event_type,
event.headers,
event.payload
])
end
defp handle_response({:ok, result}, stream_id) do
Logger.info "appended #{result} events to stream id #{stream_id}"
{:ok, result}
end
defp handle_response({:error, reason}, stream_id) do
Logger.warn "failed to append events to stream id #{stream_id} due to #{reason}"
{:error, reason}
end
defmodule Query do
def latest_version(conn, stream_id) do
conn
|> Postgrex.query(Statements.query_latest_version, [stream_id])
|> handle_response
end
defp handle_response({:ok, %Postgrex.Result{num_rows: 0}}) do
# no stored events, so latest version is 0
{:ok, 0}
end
defp handle_response({:ok, %Postgrex.Result{rows: rows}}) do
latest_version = rows |> List.first |> List.first
{:ok, latest_version}
end
end
end