Packages
one_piece_commanded
0.21.2
0.26.0
0.25.2
0.25.1
0.25.0
0.24.0
0.23.0
0.22.0
0.21.4
0.21.3
0.21.2
0.21.1
0.21.0
0.20.0
0.19.1
0.19.0
0.18.0
0.17.0
0.16.0
0.15.2
0.15.1
0.15.0
0.14.0
0.13.0
0.12.0
0.11.1
0.11.0
0.10.1
0.10.0
0.9.1
0.9.0
0.8.0
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.0
0.5.0
0.4.1
0.4.0
0.3.0
0.2.1
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Extend Commanded package
Current section
Files
Jump to
Current section
Files
lib/one_piece/commanded/helpers.ex
defmodule OnePiece.Commanded.Helpers do
@moduledoc """
A Swiss Army Knife Helper Module.
"""
alias Commanded.Event.FailureContext
@type error_context :: FailureContext.t() | map()
@type commanded_dispatch_response ::
:ok
| {:ok, aggregate_state :: struct()}
| {:ok, aggregate_version :: non_neg_integer()}
| {:ok, execution_result :: Commanded.Commands.ExecutionResult.t()}
| {:error, :unregistered_command}
| {:error, :consistency_timeout}
| {:error, reason :: term()}
@doc """
Deprecated, it has the same behavior as `OnePiece.Commanded.Id.new/0`.
"""
@spec generate_uuid :: String.t()
@deprecated "Use `OnePiece.Commanded.Id.new/0` instead."
defdelegate generate_uuid, to: OnePiece.Commanded.Id, as: :new
@doc """
Transforms the given `source` map or struct into the `target` struct.
"""
@spec struct_from(source :: struct(), target :: struct()) :: struct()
def struct_from(%_{} = source, target) do
struct(target, Map.from_struct(source))
end
@spec struct_from(attrs :: map(), target :: module()) :: struct()
def struct_from(attrs, target) do
struct(target, attrs)
end
@doc false
@spec defines_struct?(mod :: module()) :: boolean()
def defines_struct?(mod) do
:functions
|> mod.__info__()
|> Keyword.get(:__struct__)
|> Kernel.!=(nil)
end
@doc """
Copy the information from the `source` map into the given `target` map.
iex> OnePiece.Commanded.Helpers.cast_to(%{}, %{name: "ubi-wan", last_name: "kenobi"}, [:last_name])
%{last_name: "kenobi"}
"""
@spec cast_to(target :: map, source :: map, keys :: [Map.key()]) :: map
def cast_to(target, source, keys) do
Map.merge(target, Map.take(source, keys))
end
@doc """
Returns a keyword list containing the "correlation id" and "causation id" tracing.
iex> OnePiece.Commanded.Helpers.tracing_from_metadata(%{
...> event_id: "26eb06fe-9ba6-4f58-a2dd-2bdba73de4f2",
...> correlation_id: "f634ba94-145c-4fa7-bf7f-0d73dd83b446"
...> })
...>
[causation_id: "26eb06fe-9ba6-4f58-a2dd-2bdba73de4f2", correlation_id: "f634ba94-145c-4fa7-bf7f-0d73dd83b446"]
Useful when dispatching commands to copy-forward `t:Commanded.Event.Handler.metadata/0` tracing information.
defmodule MyProcessor do
application: MyApp,
use Commanded.Event.Handler,
name: "my_processor"
alias OnePiece.Commanded.Helpers
def handle(%MyEvent{} = event, metadata) do
MyApp.dispatch(
%MyCommand{},
# copy-forward the information
Helpers.tracing_from_metadata(metadata)
)
end
end
"""
@spec tracing_from_metadata(metadata :: Commanded.Event.Handler.metadata()) :: [
causation_id: String.t(),
correlation_id: String.t()
]
def tracing_from_metadata(metadata) do
[causation_id: metadata.event_id, correlation_id: metadata.correlation_id]
end
@doc """
Adds the "correlation id" and "causation id" tracing to an existing keyword list configuration option.
iex> OnePiece.Commanded.Helpers.tracing_from_metadata([timeout: 30_000], %{
...> event_id: "26eb06fe-9ba6-4f58-a2dd-2bdba73de4f2",
...> correlation_id: "f634ba94-145c-4fa7-bf7f-0d73dd83b446"
...> })
...>
[timeout: 30_000, causation_id: "26eb06fe-9ba6-4f58-a2dd-2bdba73de4f2", correlation_id: "f634ba94-145c-4fa7-bf7f-0d73dd83b446"]
Useful when dispatching commands to copy-forward the `t:Commanded.Event.Handler.metadata/0` tracing information and
wants to also add other keyword list options.
defmodule MyProcessor do
use Commanded.Event.Handler,
application: MyApp,
name: "my_processor"
alias OnePiece.Commanded.Helpers
def handle(%MyEvent{} = event, metadata) do
MyApp.dispatch(
%MyCommand{},
# copy-forward the information
Helpers.tracing_from_metadata([timeout: 30_000], metadata)
)
end
end
"""
@spec tracing_from_metadata(opts :: keyword, metadata :: Commanded.Event.Handler.metadata()) :: [
causation_id: String.t(),
correlation_id: String.t()
]
def tracing_from_metadata(opts, metadata) do
Keyword.merge(opts, tracing_from_metadata(metadata))
end
@doc """
Returns `skip` or a `retry` response.
When the `c:Commanded.Application.dispatch/1` or `c:Commanded.Application.dispatch/2` returns an `:skip` otherwise,
returns a `:retry` response. Useful when you are doing error handling in your `c:Commanded.Event.Handler.error/3`.
iex> success_dispatch = fn _ -> :ok end
...> OnePiece.Commanded.Helpers.skip_or_retry(success_dispatch.(%{}), %{})
:skip
iex> success_dispatch = fn _ -> {:ok, %{}} end
...> OnePiece.Commanded.Helpers.skip_or_retry(success_dispatch.(%{}), %{})
:skip
iex> failure_dispatch = fn _ -> {:error, :ooops} end
...> OnePiece.Commanded.Helpers.skip_or_retry(failure_dispatch.(%{}), %{failures: 1})
{:retry, %{failures: 1}}
"""
@spec skip_or_retry(tuple_response :: commanded_dispatch_response, context :: error_context) ::
:skip | {:retry, error_context}
def skip_or_retry(:ok, _context), do: :skip
def skip_or_retry({:ok, _}, _context), do: :skip
def skip_or_retry(_, context), do: {:retry, context}
@doc """
Returns `skip` or a `retry` response with a given delay.
When the `c:Commanded.Application.dispatch/1` or `c:Commanded.Application.dispatch/2` returns an `:skip` otherwise,
returns a `:retry` response. Useful when you are doing error handling in your `c:Commanded.Event.Handler.error/3`.
iex> success_dispatch = fn _ -> :ok end
...> OnePiece.Commanded.Helpers.skip_or_retry(success_dispatch.(%{}), 5_000, %{})
:skip
iex> success_dispatch = fn _ -> {:ok, %{}} end
...> OnePiece.Commanded.Helpers.skip_or_retry(success_dispatch.(%{}), 5_000, %{})
:skip
iex> failure_dispatch = fn _ -> {:error, :ooops} end
...> OnePiece.Commanded.Helpers.skip_or_retry(failure_dispatch.(%{}), 5_000, %{failures: 1})
{:retry, 5_000, %{failures: 1}}
"""
@spec skip_or_retry(
tuple_response :: commanded_dispatch_response,
delay :: non_neg_integer(),
context :: error_context
) ::
:skip | {:retry, non_neg_integer(), error_context}
def skip_or_retry(:ok, _delay, _context), do: :skip
def skip_or_retry({:ok, _}, _delay, _context), do: :skip
def skip_or_retry(_, delay, context), do: {:retry, delay, context}
@doc """
Increase the failure counter from `t:Commanded.Event.FailureContext.t/0` context by one.
iex> OnePiece.Commanded.Helpers.increase_failure_counter(%Commanded.Event.FailureContext{context: %{failures_count: 1}})
%{failures_count: 2}
"""
@spec increase_failure_counter(failure_context :: FailureContext.t()) :: map()
def increase_failure_counter(%FailureContext{} = failure_context) do
Map.update(failure_context.context, :failures_count, 1, &(&1 + 1))
end
@doc """
Ignores a specific error from a command dispatch result.
This function takes a dispatch result and an error term. If the result represents an error that matches the provided
error term, the function returns `:ok`. Otherwise, it returns the original result.
This is useful when an error condition should be treated as a successful operation under specific circumstances.
## Examples
iex> OnePiece.Commanded.Helpers.ignore_error({:error, :idempotency_failure}, :idempotency_failure)
:ok
iex> OnePiece.Commanded.Helpers.ignore_error({:error, :something_went_wrong}, :idempotency_failure)
{:error, :something_went_wrong}
iex> OnePiece.Commanded.Helpers.ignore_error(:ok, :idempotency_failure)
:ok
iex> OnePiece.Commanded.Helpers.ignore_error({:ok, %{name: "Billy"}}, :idempotency_failure)
{:ok, %{name: "Billy"}}
"""
@spec ignore_error(result :: commanded_dispatch_response, error: any) :: commanded_dispatch_response
def ignore_error({:error, expected_error}, expected_error), do: :ok
def ignore_error(result, _expected_error), do: result
@doc false
def get_primary_key({identifier, identifier_type}) do
{identifier, identifier_type}
end
def get_primary_key(identifier) when is_atom(identifier) do
{identifier, :string}
end
end