Getting started
In Sea, you define signal and a bunch of observers that get called upon signal emission:
defmodule InvoiceCreatedSignal do
use Sea.Signal
emit_to AnalyticsObserver
defstruct [:invoice_number]
end
defmodule AnalyticsObserver do
use Sea.Observer
@impl true
def handle_signal(%InvoiceCreatedSignal{invoice_number: invoice_number}) do
IO.puts("Adding invoice #{invoice_number} to analytics accumulators")
end
end
%InvoiceCreatedSignal{invoice_number: "2019/1"}
|> Sea.Signal.emit()
At its core, Sea.Signal.emit/1
takes your signal struct and emits it to defined observers by
calling Sea.Observer.handle_signal/1
in each of them with the specific signal payload. Observer
calls are made synchronously (which makes the operations belong to the same process and same
database transaction) and in order (which makes it easy to reason about the whole flow and to debug
it if needed).
You wouldn’t be far from truth by saying that Sea is just a single Enum.each/2
loop (and it can
actually be found in Sea.Signal.emit/1
code) with a contract and fancy philosophy around it.