DDD.Query behaviour (vicander v0.1.1)

Copy Markdown View Source

A query is the input of one read use case, and it carries its own handler.

Like a DDD.Command it declares fields (validated on construction) and implements execute/1, but it only reads: it never saves an aggregate or emits an event.

defmodule MyApp.Queries.OpenItems do
  use DDD.Query

  query do
    description "The items of a list that are not done yet"
    field :list_id, :uuid, required: true, description: "The list to read"
  end

  @impl DDD.Query
  def execute(%__MODULE__{list_id: id}) do
    with {:ok, list} <- MyApp.ListRepository.impl().find_by_id(id) do
      {:ok, for(item <- list.items, item.status == :open, do: %{id: item.id})}
    end
  end
end

MyApp.Queries.OpenItems.call(%{list_id: id})   # => {:ok, [%{id: ...}]}

new/1, call/1 and call!/1 work as for commands, and an error comes back as a DDD.Error whose context has :query set to the query module.

Read-only transaction

execute/1 runs in a read-only database transaction by default. Postgres refuses any write inside it, so "this cannot change data" is enforced by the database. The transaction also gives the query one consistent snapshot, and it is always rolled back, never committed.

use DDD.Query                          # read-only transaction on the configured repo
use DDD.Query, repo: MyApp.Repo        # another repo
use DDD.Query, transaction: false      # no transaction (in-memory repositories, APIs)

A query called from inside an open transaction (for example from a command) joins it and cannot make it read-only.

Summary

Callbacks

execute(query)

@callback execute(query :: struct()) :: {:ok, any()} | {:error, any()}

Functions

query(list)

(macro)