Search code examples
apache-kafkaerlangkafka-consumer-apibehaviorerlang-supervisor

Are Erlang behaviours really kind of the same as class inheritance?


Currently I am working on a data pipeline project utilizing Apache Kafka and Erlang as a stream processor language (I have actually no previous experience with erlang except learning the language and the concepts since the beginning of the year). To write Kafka consumers we rely on the rock solid brod module.

I understand, that I have to write a supervisor callback module which is responsible for starting my brod_client and my group_consumer module.

my_app
  +-- my_sup
        +-- brod_client
        +-- my_group_consumer

this group_consumer module I wrote is a callback module for the brod_group_subsriber_v2 behaviour which in itself is a callback module for gen_server.

my_group_consumer > brod_group_subscriber > gen_server

When I run my application, my supervisor is starting the brod_client, but not my consumer erlang:whereis(my_group_consumer). returns undefined. Only after a message reaches in, the brod_group_consumer_v2 itself seems to intitalizes my_group_consumer and calls its message handler function. This "lazy" init makes sense for me, but differs from what I would expect as I configured the supervisor to explicitely take care of my_group_consumer (not its base behaviour).

So after all this I am not sure if I understand behaviours exactly and therefore if I am using the brod modules correctly, as I try to inherit from them as I would with Java class inheritance and polymorphism.

Its very hard to find examples where brod is used in a supervisor and not just on the shell for demo purposes.

EDIT: I am using brod as an example of a situation/use case here, where I expected the implementation of most specialised module to "inherit" from the mst generic one, which doesn't seem to be the case and therefore I am not able to use a module like brod correctly.

Would somebody please explain me the differences of the concepts? If you like to explain it according to my example it would be great, if you can explain it with another example or no example at all...also fine, thanks in advance.


Solution

  • The same module can be a callback module for a behavior and define another. If an existing behavior takes you half the way there, you can just build your new behavior on top of it, like OTP does ;)"

    ... I´d wish there would be an Third Article covering exactly that ;)

    The following example works for me--it implements a my_server behaviour on top of gen_server:

    my_server.erl:

    -module(my_server).
    -compile(export_all).
    
    -behaviour(gen_server).
    -callback go( integer() ) -> atom().  %% Specifies that this module is a behaviour
                                          %% which has one required callback function go/1
    
    %%%   required gen_server callback functions
    
    init(_Args) ->
        {ok, []}.
    
    handle_call(Msg, _From, State) ->
        io:format("In handle_call(), Msg= ~w~n", [Msg]),
        {reply, hello_from_handle_call, State}.
    
    handle_cast(Msg, State) ->
        io:format("In handle_cast(), Msg= ~w~n", [Msg]),
        {noreply, State}.
    
    handle_info(Msg, State) ->
        io:format("In handle_info(), Msg= ~w~n", [Msg]),
        {noreply, State}.
    

    b.erl:

    -module(b).
    -compile(export_all).
    
    -behaviour(my_server).
    
    %%  my_server callback functions
    
    go(_Count) ->
        ?MODULE ! hi_from_go,  %% handle_info() gets this message
        hi.
    
    %% client functions
    
    start() ->
        gen_server:start_link( %% Calls the init() gen_server callback function.
          {local, ?MODULE},    %% Registers the gen_server using this name.
          my_server,           %% Looks for the gen_server callback functions in this module.
          [],
          []
        ).
    
    do_call() ->
        spawn(
          fun() -> gen_server:call(?MODULE, hello) end  %% handle_call() gets this message
        ).
    
    do_go() ->
        spawn(
          fun() -> go(20) end
        ).
    

    In the shell:

    1> c(my_server).
    my_server.erl:2: Warning: export_all flag enabled - all functions will be exported
    {ok,my_server}
    
    2> c(b).
    b.erl:2: Warning: export_all flag enabled - all functions will be exported
    {ok,b}
    
    3> b:start().
    {ok,<0.76.0>}
    
    4> b:do_call().
    In handle_call(), Msg= hello
    <0.78.0>
    
    5> b:do_go().
    In handle_info(), Msg= hi_from_go
    <0.80.0>