Current section

Files

Jump to
evoq src evoq_execution_context.erl
Raw

src/evoq_execution_context.erl

%% @doc Execution context for command dispatch.
%%
%% Tracks command execution state through the middleware pipeline,
%% including retry attempts, consistency requirements, and metadata.
%%
%% @author rgfaber
-module(evoq_execution_context).
-include("evoq.hrl").
%% API
-export([new/1, new/2]).
-export([retry/1, can_retry/1]).
-export([get/2, put/3]).
%%====================================================================
%% API
%%====================================================================
%% @doc Create a new execution context from a command.
-spec new(#evoq_command{}) -> #evoq_execution_context{}.
new(Command) ->
new(Command, #{}).
%% @doc Create a new execution context with options.
%%
%% The store_id option determines which ReckonDB store to use.
%% If not specified, falls back to the application env configuration.
-spec new(#evoq_command{}, map()) -> #evoq_execution_context{}.
new(Command, Opts) ->
StoreId = maps:get(store_id, Opts, application:get_env(evoq, store_id, default_store)),
#evoq_execution_context{
command_id = Command#evoq_command.command_id,
causation_id = Command#evoq_command.causation_id,
correlation_id = Command#evoq_command.correlation_id,
aggregate_id = Command#evoq_command.aggregate_id,
aggregate_type = Command#evoq_command.aggregate_type,
store_id = StoreId,
expected_version = maps:get(expected_version, Opts, -1),
retry_attempts = maps:get(retry_attempts, Opts, ?DEFAULT_RETRY_ATTEMPTS),
consistency = maps:get(consistency, Opts, eventual),
timeout = maps:get(timeout, Opts, ?DEFAULT_TIMEOUT),
metadata = Command#evoq_command.metadata
}.
%% @doc Attempt to retry the command.
%% Returns {ok, NewContext} if retries remain, {error, too_many_attempts} otherwise.
-spec retry(#evoq_execution_context{}) ->
{ok, #evoq_execution_context{}} | {error, too_many_attempts}.
retry(#evoq_execution_context{retry_attempts = 0}) ->
{error, too_many_attempts};
retry(#evoq_execution_context{retry_attempts = N} = Context) ->
{ok, Context#evoq_execution_context{retry_attempts = N - 1}}.
%% @doc Check if retries are available.
-spec can_retry(#evoq_execution_context{}) -> boolean().
can_retry(#evoq_execution_context{retry_attempts = N}) ->
N > 0.
%% @doc Get a value from the context metadata.
-spec get(atom(), #evoq_execution_context{}) -> term() | undefined.
get(Key, #evoq_execution_context{metadata = Meta}) ->
maps:get(Key, Meta, undefined).
%% @doc Put a value into the context metadata.
-spec put(atom(), term(), #evoq_execution_context{}) -> #evoq_execution_context{}.
put(Key, Value, #evoq_execution_context{metadata = Meta} = Context) ->
Context#evoq_execution_context{metadata = Meta#{Key => Value}}.