lib/kvasir/storage/freezer.ex

defmodule Kvasir.Storage.Freezer.Hooks do
  @callback pre(storage :: atom, topic :: Kvasir.Topic.t(), event :: Kvasir.Event.t()) ::
              :ok | {:error, atom}
  @callback post(storage :: atom, topic :: Kvasir.Topic.t(), event :: Kvasir.Event.t()) ::
              :ok | {:error, atom}
end

defmodule Kvasir.Storage.Freezer.HookImpl do
  @moduledoc false
  @behaviour Kvasir.Storage.Freezer.Hooks
  def pre(storage, topic, event)
  def pre(_storage, _topic, _event), do: :ok

  def post(storage, topic, event)
  def post(_storage, _topic, _event), do: :ok
end

defmodule Kvasir.Storage.Freezer do
  @spec configure_hooks(module :: module) :: :ok
  def configure_hooks(module) do
    Code.compiler_options(ignore_module_conflict: true)

    Code.compile_quoted(
      quote do
        defmodule Kvasir.Storage.Freezer.HookImpl do
          @moduledoc false
          @behaviour Kvasir.Storage.Freezer.Hooks
          defdelegate pre(storage, topic, event), to: unquote(module)
          defdelegate post(storage, topic, event), to: unquote(module)
        end
      end
    )

    Code.compiler_options(ignore_module_conflict: false)

    :ok
  end

  defmacro __using__(opts \\ []) do
    source = opts[:source] || raise "Need to set EventSource."
    topic = opts[:topic]
    {storage, storage_name} = opts[:storage]

    quote do
      @doc false
      @spec child_spec(Keyword.t()) :: map
      def child_spec(_opts \\ []) do
        %{
          id: __MODULE__,
          start: {__MODULE__, :start_link, []}
        }
      end

      @doc false
      @spec start_link :: {:ok, pid} | {:error, term}
      def start_link do
        unquote(source).subscribe(unquote(topic), __MODULE__, group: inspect(__MODULE__))
      end

      @doc false
      @spec init(Kvasir.Topic.t(), non_neg_integer, term) :: {:ok, Kvasir.Topic.t()}
      def init(topic, _partition, _opts), do: {:ok, topic}

      @doc false
      @spec event(Kvasir.Event.t(), Kvasir.Topic.t()) :: :ok | {:error, atom}
      def event(event, topic) do
        with :ok <- unquote(__MODULE__.HookImpl).pre(unquote(storage_name), topic, event),
             :ok <- unquote(storage).freeze(unquote(storage_name), topic, event),
             :ok <- unquote(__MODULE__.HookImpl).post(unquote(storage_name), topic, event),
             do: :ok
      end
    end
  end
end