A simple gen_event example.

September 14, 2016

The gen_event behavior makes it simple to add basic event sourcing to your Erlang application.

Young couple walking through a closed, colorful, carnival. class=
© 2016 Tim Gouw for Unsplash

In OTP, an event manager is a named object to which events can be sent. An event can be, for example, an error, an alarm, or some information that is to be logged.

In the event manager, zero, one, or many event handlers are installed. When the event manager is notified about an event, the event is processed by all the installed event handlers.

There are four things you need to do to use a OTP event manager:

  1. write an event handler,
  2. start and name the event manager,
  3. add a handler to the manager,
  4. and notify the manager of an event

1. Write an event handler

This is counter module is an event handler that counts events and prints status updates to stdout.



-export([code_change/3, handle_call/2, handle_event/2,
         handle_info/2, init/1, terminate/2]).

init(_Args) ->
    io:put_chars("** 0: inititalize to zero\n"), {ok, 0}.

handle_event(Event, Count) ->
    io:format("** +1: got event ~p~n", [Event]),
    {ok, Count + 1}.

handle_call(Request, Count) ->
    io:format("** got request ~p~n", [Request]),
    {ok, Count, Count}.

code_change(_OldVsn, State, _Extra) -> {ok, State}.

handle_info(_Info, State) -> {noreply, State}.

terminate(_Args, _State) -> ok.

In this case, the state is simply the Count of events that have been received. This could be as complex as you like.

(Note that the state is passed to the code_change event, so if you release a new version of the handler and need to change the state shape, you can transform the old state to the new as part of the upgrade.)

2. Start and name the event manager

We name the event manager “event_dispatcher”.

    {ok, _Pid} = gen_event:start_link({local, event_dispatcher}),

3. Add a handler to the manager

After this, events are sent to the counter’s handle_event/2 method.

    gen_event:add_handler(event_dispatcher, counter, []),

4. Notify the manager of an event

An Event can be any Erlang term.

    Event = {an, event, {can, [be, any, erlang]}, term},
    ok = gen_event:notify(event_dispatcher, Event),

Getting at the state in the handler

With one additional step, the gen_event behavior makes it trivial to create a CQRS read modelWhat is CQRS? in your application.

You need to get the data out of the event handler!

    Request = {so, can, the, call, request},
    Count = gen_event:call(event_dispatcher, counter, Request),

The gen_event:call/3 gives you synchronous access to the read model state.

Putting it all together

Using the counter model exactly as shown above, we can write an escript to show all of these steps.

$ cat run.escript 
#! /usr/bin/env escript

main(_Argv) ->
    {ok, _Pid} = gen_event:start_link({local, event_dispatcher}),
    gen_event:add_handler(event_dispatcher, counter, []),
    Event = {an, event, {can, [be, any, erlang]}, term},
    ok = gen_event:notify(event_dispatcher, Event),
    Request = {so, can, the, call, request},
    Count = gen_event:call(event_dispatcher, counter, Request),
    io:format("Event count = ~w~n", [Count]).

$ cat Makefile 
.PHONY: run
run: counter.beam

%.beam: %.erl
	erlc $?

$ make
** 0: inititalize to zero
** +1: got event {an,event,{can,[be,any,erlang]},term}
** got request {so,can,the,call,request}
Event count = 1


A major caveat …

Unlike the gen_server,“Gen_server is usually spawned as a separate process, and all user callbacks (such as Mod:handle_call/3, Mod:handle_cast/2, etc) are executed in the context of that process.” Gen Event Behavior Demystified, by Serge Aleynikov (retrieved Sep. 14, 2016) the event manager executes all it’s handlers in the process context of the event manager. The event manager protects itself against handler code that crashes by silently removing the handler.

If you are using a handler as a read model as I suggest above, silent removal will break your application. So, either you need to write a bullet-proof handler or you need to come up with an approach that if a read model crashes, it does not miss any events. But that’s a topic for another blog.

Some resources

Change Log

Nov. 24, 2016