Packages

Fault-tolerant dataflow DSL

Current section

Files

Jump to
kflow src framework kflow_gen_map.erl
Raw

src/framework/kflow_gen_map.erl

%%%===================================================================
%%% @copyright 2019 Klarna Bank AB (publ)
%%%
%%% @doc This module implements `map'-like stream processing node that
%%% applies a pure function to each incoming message.
%%%
%%% This behavior can be used in two modes: full and simplified. In
%%% simplified mode stream processing node is defined like following:
%%%
%%% ```{map, fun(Offset, Message) -> Message end}'''
%%%
%%% In full mode one has to create a callback module with
%%% `kflow_gen_map' behavior.
%%%
%%% `map' callback takes 3 arguments: first is offset of a message,
%%% second is the message itself and the third one is state of the
%%% callback module. This state is created in `init' callback and
%%% remains the same through the lifetime of the pipe. Return value of
%%% `map' callback is a processed message that is passed downstream.
%%%
%%% `init' and `terminate' callbacks can be used e.g. when some
%%% resource should be obtained to process messages. Both callbacks
%%% are optional; configuration will be passed as is to
%%% `map' callback when `init' is omitted.
%%%
%%% == Example ==
%%% ```
%%% -module(my_map).
%%%
%%% -behavior(kflow_gen_map).
%%%
%%% -export([init/1, map/3, terminate/1]).
%%%
%%% init(Config) ->
%%% State = do_init(Config),
%%% State.
%%%
%%% map(Offset, Message, State) ->
%%% Message.
%%%
%%% terminate(State) ->
%%% do_cleanup(State).
%%%
%%% '''
%%%
%%% NOTE: Since state is immutable, it's actually shared between the
%%% routes.
%%%
%%% @end
-module(kflow_gen_map).
-behavior(kflow_gen).
-include("kflow.hrl").
-include_lib("hut/include/hut.hrl").
-export([init/2, handle_message/3, handle_flush/2, terminate/2]).
-export_type([callback_fun/0]).
-callback init(_Config) -> _State.
-callback map(kflow:offset(), _DataIn, _State) -> _DataOut.
-callback terminate(_State) -> _.
-optional_callbacks([init/1, terminate/1]).
-type callback_fun() :: fun((kflow:offset(), _InputMessage) -> _OutputMessage).
-record(s1,
{ cb_module :: module()
, cb_state :: term()
}).
-record(s2,
{ function :: callback_fun()
}).
-type state() :: #s1{} | #s2{}.
%% @private
init(_NodeId, {?MODULE, Fun}) when is_function(Fun) ->
is_function(Fun, 2) orelse error({badarity, Fun}),
{ok, #s2{ function = Fun
}};
init(_NodeId, {CbModule, CbConfig}) ->
CbState = kflow_lib:optional_callback(CbModule, init, [CbConfig], CbConfig),
{ok, #s1{ cb_module = CbModule
, cb_state = CbState
}}.
%% @private
handle_message(Msg = #kflow_msg{hidden = true}, State, _) ->
%% Don't execute callback for a hidden message, simply pass it downstream:
{ok, [Msg], State};
handle_message(Msg0, State, _) ->
#kflow_msg{payload = Payload0, offset = Offset} = Msg0,
Payload = case State of
#s1{cb_module = CbModule, cb_state = CbState} ->
CbModule:map(Offset, Payload0, CbState);
#s2{function = Fun} ->
Fun(Offset, Payload0)
end,
Msg = Msg0#kflow_msg{payload = Payload},
{ok, [Msg], State}.
%% @private
handle_flush(State, _) ->
{ok, [], State}.
%% @private
terminate(#s1{cb_state = CbState, cb_module = CbModule}, _) ->
kflow_lib:optional_callback(CbModule, terminate, [CbState]);
terminate(#s2{}, _) ->
ok.