Packages
cqrs_tools
0.4.3
0.5.28
0.5.27
0.5.26
0.5.25
0.5.24
0.5.23
0.5.22
0.5.21
0.5.20
0.5.19
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.22
0.3.21
0.3.20
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.30
0.2.29
0.2.28
0.2.27
0.2.26
0.2.25
0.2.24
0.2.23
0.2.22
0.2.21
0.2.20
0.2.19
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.1
0.1.0
A collection of handy Elixir macros for CQRS applications.
Current section
Files
Jump to
Current section
Files
lib/cqrs/bounded_context.ex
defmodule Cqrs.BoundedContext do
alias Cqrs.{BoundedContext, Guards}
@moduledoc """
Macros to create proxy functions to [commands](`Cqrs.Command`) and [queries](`Cqrs.Query`) in a module.
## Examples
defmodule Users do
use Cqrs.BoundedContext
command CreateUser
command CreateUser, as: :create_user2
query GetUser
query GetUser, as: :get_user2
end
### Commands
iex> {:error, {:invalid_command, errors}} = Users.create_user(name: "chris", email: "wrong")
...> errors
%{email: ["has invalid format"]}
iex> {:error, {:invalid_command, errors}} = Users.create_user2(name: "chris", email: "wrong")
...> errors
%{email: ["has invalid format"]}
iex> Users.create_user(name: "chris", email: "chris@example.com")
{:ok, :dispatched}
iex> Users.create_user2(name: "chris", email: "chris@example.com")
{:ok, :dispatched}
### Queries
iex> Users.get_user!(email: "wrong")
** (Cqrs.QueryError) email has invalid format
iex> {:error, errors} = Users.get_user2(%{bad: "data"})
...> errors
%{email: ["can't be blank"]}
iex> {:error, errors} = Users.get_user(email: "wrong")
...> errors
%{email: ["has invalid format"]}
iex> {:ok, query} = Users.get_user_query(email: "chris@example.com")
...> query
#Ecto.Query<from u0 in User, where: u0.email == ^"chris@example.com">
iex> {:ok, user} = Users.get_user(email: "chris@example.com")
...> %{id: user.id, email: user.email}
%{id: "052c1984-74c9-522f-858f-f04f1d4cc786", email: "chris@example.com"}
"""
defmacro __using__(_) do
quote do
import BoundedContext, only: [command: 1, command: 2, query: 1, query: 2]
end
end
@doc """
Creates proxy functions to dispatch this command module.
## Functions created
When given `CreateUser`
* `create_user!/0`
* `create_user!/1`
* `create_user!/2`
* `create_user/0`
* `create_user/1`
* `create_user/2`
## Options
* `:then` - A function of one arity to run with the execution result.
"""
defmacro command(command_module, opts \\ []) do
opts = Macro.escape(opts)
function_head =
quote do
function_name = BoundedContext.__function_name__(unquote(command_module), unquote(opts))
BoundedContext.__command_proxy_function_head__(unquote(command_module), function_name)
end
proxies =
quote location: :keep do
function_name = BoundedContext.__function_name__(unquote(command_module), unquote(opts))
Guards.ensure_is_command!(unquote(command_module))
BoundedContext.__command_proxy__(unquote(command_module), function_name, unquote(opts))
end
quote do
Module.eval_quoted(__ENV__, [unquote(function_head), unquote(proxies)])
end
end
def __command_proxy_function_head__(command_module, function_name) do
quote do
required_fields = unquote(command_module).__required_fields__()
if length(required_fields) > 0 do
def unquote(function_name)(attrs, opts \\ [])
def unquote(:"#{function_name}!")(attrs, opts \\ [])
else
def unquote(function_name)(attrs \\ [], opts \\ [])
def unquote(:"#{function_name}!")(attrs \\ [], opts \\ [])
end
end
end
def __command_proxy__(command_module, function_name, opts) do
quote do
@doc """
#{unquote(command_module).__module_docs__()}
"""
def unquote(function_name)(attrs, opts) do
opts = Keyword.merge(unquote(opts), Cqrs.Options.normalize(opts))
BoundedContext.__dispatch_command__(unquote(command_module), attrs, opts)
end
@doc """
#{unquote(command_module).__module_docs__()}
"""
def unquote(:"#{function_name}!")(attrs, opts) do
opts = Keyword.merge(unquote(opts), Cqrs.Options.normalize(opts))
BoundedContext.__dispatch_command__!(unquote(command_module), attrs, opts)
end
end
end
@doc """
Creates proxy functions to create and execute the give query.
## Functions created
When given `ListUsers`
* `list_users!/0`
* `list_users!/1`
* `list_users!/2`
* `list_users/0`
* `list_users/1`
* `list_users/2`
* `list_users_query!/0`
* `list_users_query!/1`
* `list_users_query!/2`
* `list_users_query/0`
* `list_users_query/1`
* `list_users_query/2`
"""
defmacro query(query_module, opts \\ []) do
opts = Macro.escape(opts)
function_head =
quote do
function_name = BoundedContext.__function_name__(unquote(query_module), unquote(opts))
BoundedContext.__query_proxy_function_head__(unquote(query_module), function_name)
end
proxies =
quote location: :keep do
Guards.ensure_is_query!(unquote(query_module))
function_name = BoundedContext.__function_name__(unquote(query_module), unquote(opts))
BoundedContext.__query_proxy__(
unquote(query_module),
function_name,
unquote(opts)
)
end
quote do
Module.eval_quoted(__ENV__, [unquote(function_head), unquote(proxies)])
end
end
def __query_proxy_function_head__(query_module, function_name) do
quote do
required_filters = unquote(query_module).__required_filters__()
if length(required_filters) > 0 do
def unquote(function_name)(filters, opts \\ [])
def unquote(:"#{function_name}!")(filters, opts \\ [])
def unquote(:"#{function_name}_query")(filters, opts \\ [])
def unquote(:"#{function_name}_query!")(filters, opts \\ [])
else
def unquote(function_name)(filters \\ [], opts \\ [])
def unquote(:"#{function_name}!")(filters \\ [], opts \\ [])
def unquote(:"#{function_name}_query")(filters \\ [], opts \\ [])
def unquote(:"#{function_name}_query!")(filters \\ [], opts \\ [])
end
end
end
def __query_proxy__(query_module, function_name, opts) do
quote do
@doc """
#{unquote(query_module).__module_docs__()}
"""
def unquote(function_name)(filters, opts) do
opts = Keyword.merge(unquote(opts), Cqrs.Options.normalize(opts))
BoundedContext.__execute_query__(unquote(query_module), filters, opts)
end
@doc """
#{unquote(query_module).__module_docs__()}
"""
def unquote(:"#{function_name}!")(filters, opts) do
opts = Keyword.merge(unquote(opts), Cqrs.Options.normalize(opts))
BoundedContext.__execute_query__!(unquote(query_module), filters, opts)
end
query_name = unquote(query_module).__name__()
query_headline_modifier = if query_name =~ ~r/^[aeiou]/i, do: "an", else: "a"
@doc """
Creates #{query_headline_modifier} [#{query_name}](`#{unquote(query_module)}`) query without executing it.
#{unquote(query_module).__module_docs__()}
"""
def unquote(:"#{function_name}_query")(filters, opts) do
BoundedContext.__create_query__(unquote(query_module), filters, opts)
end
@doc """
Creates #{query_headline_modifier} [#{query_name}](`#{unquote(query_module)}`) query without executing it.
#{unquote(query_module).__module_docs__()}
"""
def unquote(:"#{function_name}_query!")(filters, opts) do
BoundedContext.__create_query__!(unquote(query_module), filters, opts)
end
end
end
def __function_name__(module, opts) do
[name | _] =
module
|> Module.split()
|> Enum.reverse()
default_function_name =
name
|> to_string
|> Macro.underscore()
|> String.to_atom()
Keyword.get(opts, :as, default_function_name)
end
def __dispatch_command__(module, attrs, opts) do
attrs
|> module.new(opts)
|> module.dispatch(opts)
|> __handle_result__(opts)
end
def __dispatch_command__!(module, attrs, opts) do
attrs
|> module.new!(opts)
|> module.dispatch(opts)
|> __handle_result__(opts)
end
def __create_query__(module, attrs, opts) do
module.new(attrs, opts)
end
def __create_query__!(module, attrs, opts) do
module.new!(attrs, opts)
end
def __execute_query__(module, attrs, opts) do
attrs
|> module.new(opts)
|> module.execute(opts)
|> __handle_result__(opts)
end
def __execute_query__!(module, attrs, opts) do
attrs
|> module.new!(opts)
|> module.execute!(opts)
|> __handle_result__(opts)
end
def __handle_result__(result, opts) do
case Keyword.get(opts, :then, &Function.identity/1) do
fun when is_function(fun, 1) -> fun.(result)
_ -> raise("'then' should be a function/1")
end
end
end