Current section

Files

Jump to
cqrs_tools lib cqrs domain_event.ex
Raw

lib/cqrs/domain_event.ex

defmodule Cqrs.DomainEvent do
@moduledoc """
Defines a new domain event struct
## Options
* `:from` _optional_ - a struct to derive fields from.
* `:with` _optional_ - a list of `atom` field names to add.
* `:drop` _optional_ - a list of `atom` field names to remove from any field derived from the struct in the `:from` option.
* `:version` _optional_ - a version value. Defaults to `1`
## Example
defmodule DeleteUser do
use Cqrs.Command
field :id, :integer
def handle_dispatch(command, _opts) do
{:ok, :no_impl}
end
end
defmodule UserDeleted do
use Cqrs.DomainEvent,
from: DeleteUser,
with: [:from],
version: 2
end
iex> cmd = DeleteUser.new!(id: 668)
...> event = UserDeleted.new(cmd, from: "chris")
...> %{id: event.id, from: event.from, version: event.version}
%{id: 668, from: "chris", version: 2}
"""
alias Cqrs.{DomainEvent, Guards}
defmacro __using__(opts) do
create_jason_encoders = Application.get_env(:cqrs_tools, :create_jason_encoders, true)
quote generated: true, location: :keep do
version = Keyword.get(unquote(opts), :version, 1)
explicit_keys = DomainEvent.explicit_keys(unquote(opts))
inherited_keys = DomainEvent.inherit_keys(unquote(opts))
keys =
Keyword.merge(inherited_keys, explicit_keys, fn
_key, nil, nil -> nil
_key, nil, value -> value
_key, value, nil -> value
end)
if unquote(create_jason_encoders) and Code.ensure_loaded?(Jason), do: @derive(Jason.Encoder)
defstruct keys
|> DomainEvent.drop_keys(unquote(opts))
|> Kernel.++([{:created_at, nil}, {:version, version}])
def new(source \\ [], attrs \\ []) do
DomainEvent.new(__MODULE__, source, attrs)
end
end
end
@doc false
def drop_keys(keys, opts) do
keys_to_drop = Keyword.get(opts, :drop, []) |> List.wrap()
Enum.reject(keys, fn
{:__struct__, _} -> true
{:created_at, _} -> true
{name, _default_value} -> Enum.member?(keys_to_drop, name)
name -> Enum.member?(keys_to_drop, name)
end)
end
@doc false
def inherit_keys(opts) do
case Keyword.get(opts, :from) do
nil ->
[]
source when is_atom(source) ->
Guards.ensure_is_struct!(source)
source
|> struct()
|> Map.to_list()
source ->
"#{source} should be a valid struct to use with DomainEvent"
end
end
@doc false
def explicit_keys(opts) do
opts
|> Keyword.get(:with, [])
|> List.wrap()
|> Enum.map(fn
field when is_tuple(field) -> field
field when is_atom(field) or is_binary(field) -> {field, nil}
end)
end
@doc false
def new(module, source, attrs) when is_atom(module) do
fields =
source
|> normalize()
|> Map.merge(normalize(attrs))
|> Map.put(:created_at, Cqrs.Clock.utc_now(module))
struct(module, fields)
end
defp normalize(values) when is_struct(values), do: Map.from_struct(values)
defp normalize(values) when is_map(values), do: values
defp normalize(values) when is_list(values), do: Enum.into(values, %{})
end