Current section

Files

Jump to
evoq src evoq_feedback.erl
Raw

src/evoq_feedback.erl

%% @doc Feedback behaviour for evoq.
%%
%% Feedback is the response to a Hope. It carries the result of command
%% execution at the responder side, enabling session-level consistency.
%%
%% Success feedback contains the post-event aggregate state so the
%% requester has immediate truth without querying remote read models.
%%
%% Error feedback contains the reason for failure.
%%
%% Naming convention: {hope}_feedback_v1
%%
%% == Required Callbacks ==
%%
%% - feedback_type() -> binary()
%% - from_result(Result) -> map()
%% - to_result(Payload) -> {ok, term()} | {error, term()}
%%
%% == Optional Callbacks ==
%%
%% - serialize(Payload) -> {ok, binary()} | {error, Reason}
%% - deserialize(Binary) -> {ok, map()} | {error, Reason}
%%
%% @author rgfaber
-module(evoq_feedback).
%% Required callbacks
%% Feedback type identifier (binary).
-callback feedback_type() -> binary().
%% Convert a command execution result to a feedback payload.
%% The payload MUST have binary keys and be JSON-serializable.
%% The payload MUST include a <<"status">> key ("ok" or "error").
-callback from_result(Result :: {ok, State :: term()} | {error, Reason :: term()}) ->
map().
%% Convert a received feedback payload back to a result.
-callback to_result(Payload :: map()) ->
{ok, State :: term()} | {error, Reason :: term()}.
%% Optional callbacks
%% Custom serialization (defaults available via default_serialize/1).
-callback serialize(Payload :: map()) -> {ok, binary()} | {error, Reason :: term()}.
%% Custom deserialization (defaults available via default_deserialize/1).
-callback deserialize(Binary :: binary()) -> {ok, map()} | {error, Reason :: term()}.
-optional_callbacks([serialize/1, deserialize/1]).
%% Default implementations
-export([default_serialize/1, default_deserialize/1]).
%% @doc Default serialization using OTP 27 json module.
-spec default_serialize(map()) -> {ok, binary()} | {error, term()}.
default_serialize(Payload) when is_map(Payload) ->
try
{ok, json:encode(Payload)}
catch
error:Reason -> {error, {json_encode_failed, Reason}}
end.
%% @doc Default deserialization using OTP 27 json module.
-spec default_deserialize(binary()) -> {ok, map()} | {error, term()}.
default_deserialize(Binary) when is_binary(Binary) ->
try
{ok, json:decode(Binary)}
catch
error:Reason -> {error, {json_decode_failed, Reason}}
end.