# `Sourced.Middleware.Domain.Event`
[🔗](https://gitlab.com/dimakula/sourced/-/blob/0.2.0/lib/sourced/middleware/domain/event.ex#L1)

Behaviour for domain event structs registered in a
`Sourced.Middleware.Domain`.

Serializing stores (such as `Sourced.EventStore.Postgres`) persist event data
as JSON and read it back as a plain string-keyed map. `c:from_map/1` rebuilds
the domain struct from that map; encoding is handled separately by the JSON
library's encoder protocol (e.g. `JSON.Encoder`).

`use Sourced.Middleware.Domain.Event` adopts the behaviour and
injects defaults for both callbacks: `c:to_type/0` derives the stored
type from the module name, and `c:from_map/1` copies the struct's known keys
out of the map — no atoms are created dynamically. Flat events need nothing
else:

    defmodule OrderShipped do
      use Sourced.Middleware.Domain.Event

      @derive JSON.Encoder
      defstruct [:order_id, :address]
    end

Events with nested structs must override `c:from_map/1`, since JSON cannot
know that `"order_items"` should become `OrderItem` structs:

    defmodule OrderPlaced do
      use Sourced.Middleware.Domain.Event

      @derive JSON.Encoder
      defstruct [:id, :order_items]

      @impl Sourced.Middleware.Domain.Event
      def from_map(map) do
        %__MODULE__{
          id: map["id"],
          order_items: Enum.map(map["order_items"], &OrderItem.from_map/1)
        }
      end
    end

Non-serializing stores (such as `Sourced.EventStore.InMemory`) keep the
struct as-is, so `c:from_map/1` is never invoked for them.

## Stored type

Each event has a stable stored type string, persisted in the `type` column and
used to look the module back up. `c:to_type/0` defaults to the full
module name (`MyApp.OrderShipped` becomes `"MyApp.OrderShipped"`); override it
to persist a different string:

    defmodule OrderCancelled do
      use Sourced.Middleware.Domain.Event

      @derive JSON.Encoder
      defstruct [:order_id]

      @impl Sourced.Middleware.Domain.Event
      def to_type, do: "order_voided"
    end

## Tags

`c:to_tags/1` derives the event's `t:Sourced.StoredEvent.tag/0`s from the struct,
and is what `Sourced.EventStore.Query` items match on. It defaults to `[]`,
so an event with no tags is only ever reachable by its type:

    defmodule OrderPlaced do
      use Sourced.Middleware.Domain.Event

      @derive JSON.Encoder
      defstruct [:id, :customer_id]

      @impl Sourced.Middleware.Domain.Event
      def to_tags(event), do: ["order:#{event.id}", "customer:#{event.customer_id}"]
    end

Tags are derived on append, when a bare struct is wrapped into a
event map. Writing that map yourself sets them directly and
`c:to_tags/1` is not consulted, so tag a hand-built event explicitly:

    %{
      type: OrderPlaced.to_type(),
      data: %OrderPlaced{id: 1, customer_id: 42},
      tags: ["order:1", "customer:42"],
      occurred_at: DateTime.utc_now()
    }

# `from_map`

```elixir
@callback from_map(map()) :: struct()
```

Rebuilds the event struct from a string-keyed map produced by a serializing
store.

# `to_tags`

```elixir
@callback to_tags(event :: struct()) :: [Sourced.StoredEvent.tag()]
```

Returns the event's tags.

Defaults to `[]`; override it to tag the event with the domain concepts it
concerns, derived from the struct's own fields.

# `to_type`

```elixir
@callback to_type() :: String.t()
```

Returns the event's stored type string.

Defaults to the full module name; override it to persist a different, stable
string.

# `cast`

```elixir
@spec cast(module :: module(), map :: map()) :: struct()
```

Default decoding: builds `module`'s struct from the string-keyed `map`,
copying only the struct's defined keys.

Keys absent from the map keep the struct's default value; map keys that are
not struct fields are ignored. Useful as a starting point inside a custom
`c:from_map/1`:

    def from_map(map) do
      event = Sourced.Middleware.Domain.Event.cast(__MODULE__, map)
      %{event | order_items: Enum.map(event.order_items, &OrderItem.from_map/1)}
    end

---

*Consult [api-reference.md](api-reference.md) for complete listing*
