Current section

Files

Jump to
evoq src evoq_fact.erl
Raw

src/evoq_fact.erl

%% @doc Fact behavior for evoq.
%%
%% Facts are integration artifacts that cross bounded context boundaries.
%% They translate domain events into serializable payloads for external
%% consumption via pg (local) or mesh (WAN).
%%
%% Key differences from domain events:
%% - Binary keys (JSON-safe), not atom keys
%% - Explicit public contract, not internal implementation detail
%% - May have different structure/name than the source event
%% - Versioned independently from the domain event
%%
%% == Required Callbacks ==
%%
%% - fact_type() -> binary()
%% - from_event(EventType, EventData, Metadata) -> {ok, Payload} | skip
%%
%% == Optional Callbacks ==
%%
%% - serialize(Payload) -> {ok, binary()} | {error, Reason}
%% - deserialize(Binary) -> {ok, map()} | {error, Reason}
%% - schema() -> map()
%%
%% @author rgfaber
-module(evoq_fact).
%% Required callbacks
-callback fact_type() -> binary().
-callback from_event(EventType :: atom(), EventData :: map(), Metadata :: map()) ->
{ok, Payload :: map()} | skip.
%% Optional callbacks
-callback serialize(Payload :: map()) -> {ok, binary()} | {error, Reason :: term()}.
-callback deserialize(Binary :: binary()) -> {ok, map()} | {error, Reason :: term()}.
-callback schema() -> map().
-optional_callbacks([serialize/1, deserialize/1, schema/0]).
%% 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.