Elixir: Writing your own Event Dispatcher
Today we are going to learn how you could build your own implementation of event dispatcher in Elixir.
NOTE: if you are looking for production-ready implementation please review “:event_bus”. This article is educational. We are not going to build a production-ready solution. Although… how knows :)
What would be our event dispatcher? It is a separate process that would listen for messages from the main application process and handle received events via subscribed listeners and subscribers.
I will call our main process :main and our event dispatcher process is :ed.
Start new event dispatcher process
Please review GenServer docs before moving forward. It’s crucial to understand how things work.
Now let’s make a skeleton for event dispatcher
We use GenServer and going to start a new process with start_link/3
and give a unique name to our process. So far, our name will be our __MODULE__ name but we will change it later. It will allow us to run many event dispatchers. Also
Note that a
GenServer
started withstart_link/3
is linked to the parent process and will exit in case of crashes from the parent
Add subscribers and listeners
Ok, our process up and running but it’s dummy. We want to subscribe listeners and subscribers for our event dispatcher.
We add 2 client functions add_subscriber/1 and add_listeners/3. They will send a message from :main process to our :ed process. GenServer.call(my_name(), ...
will send it to :ed process. Our unique name will help to send the signal to the right process.
And server handle_call/3 functions will be performed in :ed process. I’m using Rsed.ListenerBag module, it will add new listeners and subscribers to a map like this.
It allows us to merge/combine and order subscribers and listeners. When we need to dispatch an event all we need to do just go through the list of all event listeners(listeners and subscribers). They are already ordered.
Dispatch event
Now we have subscribers and listeners and can dispatch an event.
In a similar way, we expose client and server functions. We ask ListenerBag for listeners for a certain event name. And then just call handlers.
Usage in your application
How we can use it in our application? We need to add our :ed process to our supervisor
But in that case, we also would need to configure it at the start but I don’t really like to do that in the Application module. And also we cannot run more than 1 :ed process because of its name not really unique and will refer to our module name Rsed.EventDispatcher. So I’m going to make one more iteration that will allow auto-configure it and
We wrap our functions with __using__
and quote
and now can use :name that would be set in use
call. It will allow us to run multiple :ed processes if needed. And also in init
function we add a logic that will call configure
function if it presents.
Note that we cannot call add_subscriber/1 and add_listener/3 — because it’s client function and we call it on a server-side or inside :ed process. Now we update our supervisor spec with our custom app event dispatcher.
worker(App.EventDispatcher, [])# in your appApp.EventDispatcher.dispatch(%Rsed.Event{name: :user_registered, data: user})# BINGO
Source
You can find the source code here https://github.com/radzserg/rsed
What is missing
- stop event propagation
- dynamic remove of subscribers and listeners
- synchronous handling
- proper testing in production
If you like that topic and want to contribute DM and let’s do it.