yggdrasil
yggdrasil copied to clipboard
Subscription and publishing server for Elixir applications.
Yggdrasil is an immense mythical tree that connects the nine worlds in Norse cosmology.
Yggdrasil
is an agnostic publisher/subscriber:
- Multi-node pubsub.
- Simple API (
subscribe/1
,unsubscribe/1
,publish/2
). -
GenServer
wrapper for handling subscriber events easily. - Several fault tolerant adapters:
Small Example
The following example uses the Elixir distribution to send the messages:
iex(1)> Yggdrasil.subscribe(name: "my_channel")
iex(2)> flush()
{:Y_CONNECTED, %Yggdrasil.Channel{...}}
and to publish a for the subscribers:
iex(3)> Yggdrasil.publish([name: "my_channel"], "message")
iex(4)> flush()
{:Y_EVENT, %Yggdrasil.Channel{...}, "message"}
When the subscriber wants to stop receiving messages, then it can unsubscribe from the channel:
iex(6)> Yggdrasil.unsubscribe(name: "my_channel")
iex(7)> flush()
{:Y_DISCONNECTED, %Yggdrasil.Channel{...}}
For convinience, Yggdrasil
is also a GenServer
wrapper for subscribing
to one or several channels e.g the following Subscriber
prints every message
it receives from the channel [name: "my_channel"]
:
defmodule Subscriber do
use Yggdrasil
def start_link do
channel = [name: "my_channel"]
Yggdrasil.start_link(__MODULE__, [channel])
end
@impl true
def handle_event(_channel, message, _state) do
IO.inspect message
{:ok, nil}
end
end
Channels
Channels, though internally use the struct Yggdrasil.Channel.t()
, they can be
any map or keyword list with the following keys:
Key | Default | Meaning |
---|---|---|
adapter |
:elixir (OTP message distribution) |
Adapter where subscribers subscribe to and publishers publish to. |
name |
no defaults | Name of the channel. Depends on the adapter. |
transformer |
:default (does nothing to the messages) |
The way the adapter encodes outgoing messages and decodes incoming messages. |
backend |
:default (Phonix.PubSub ) |
The way the messages are distributed across nodes. |
namespace |
nil |
Name of the configuration of the adapter. This allows several configurations for the same adapter e.g. two different PostgreSQL databases. |
This means that the channel [name: "my_channel"]
actually translates to:
%Yggdrasil.Channel{
adapter: :elixir,
name: "my_channel",
transformer: :default,
backend: :default,
namespace: nil
}
Adapters
An adapter is the implementation of the behaviour Yggdrasil.Adapter
. This
behaviour depends on two other behaviours:
-
Yggdrasil.Subscriber.Adapter
for implementing subscribers for a specific adapter. -
Yggdrasil.Publisher.Adapter
for implementing publishers for a specific adapter.
The following are the available adapters and their respective projects:
Adapter | Yggdrasil Adapter | Dependencies | Description |
---|---|---|---|
OTP | :elixir (default) |
:yggdrasil |
Multi node subscriptions |
OTP | :bridge |
:yggdrasil |
Converts any adapter to multi node |
RabbitMQ | :rabbitmq |
:yggdrasil_rabbitmq |
Fault tolerant pubsub for exchanges and routing keys |
Redis | :redis |
:yggdrasil_redis |
Fault tolerant pubsub for Redis channels |
PostgreSQL | :postgres |
:yggdrasil_postgres |
Fault tolerant pubsub for PG_NOTIFY messages |
GraphQL | :graphql |
:yggdrasil_graphql |
Converts any adapter to a GraphQL subscription |
For more information on how to use them, check the corresponding repository documentation.
Transformers
A transformer is the implementation of the behaviour Yggdrasil.Transformer
.
In essence implements two functions:
-
decode/2
for decoding messages coming from the adapter. -
encode/2
for encoding messages going to the adapter
Yggdrasil
has two implemented transformers:
Transformer | Description |
---|---|
:default |
Does nothing to the messages and it is the default transformer used if no transformer has been defined. |
:json |
Transforms from Elixir maps to string JSONs and viceversa. |
Backends
A backend is the implementation of the behaviour Yggdrasil.Backend
. The
module is in charge of distributing the messages with a certain format inside
Yggdrasil
. Currently, there is only one backend, :default
, and it is used
by default. It uses Phoenix.PubSub
to distribute the messages.
The messages received by the subscribers when using :default
backend are:
-
{:Y_CONNECTED, Yggdrasil.Channel.t()}
when the connection with the adapter is established. -
{:Y_EVENT, Yggdrasil.Channel.t(), message()}
when a message is received from the adapter. -
{:Y_DISCONNECTED, Yggdrasil.Channel.t()}
when the connection with the adapter is finished due to disconnection or unsubscription.
This backend is the only backend supported by
Yggdrasil
behaviour.
Multi node
Though :elixir
adapter has built-in support for multi node pubsub, that's not
the case with the other adapters. To overcome this limitation, :bridge
adapter is capable of connecting to a remote adapter if and only if this adapter
is not present in the current node e.g. let's say we have the following
scenario:
- Node A has
:yggdrasil
. - Node B has
:yggdrasil_rabbitmq
. - The nodes A and B are connected.
Then is possible for the node A to connect to RabbitMQ through node B by using
the :bridge
adapter:
For subscription:
iex(1)> channel = [
...(1)> name: [name: {"amq.topic", "foo"}, adapter: :rabbitmq],
...(1)> adapter: :bridge
...(1)> ]
iex(2)> Yggdrasil.subscribe(channel)
iex(3)> flush()
{:Y_CONNECTED, %Yggdrasil.Channel{(...)}}
and for publishing:
iex(1)> channel = [
...(1)> name: [name: {"amq.topic", "foo"}, adapter: :rabbitmq],
...(1)> adapter: :bridge
...(1)> ]
iex(2)> Yggdrasil.publish(channel, "bar")
:ok
Configuration
Yggdrasil
works out of the box with no special configuration at all. However,
it is possible to tune the publisher pool:
Option | Default | Description |
---|---|---|
publisher_options |
[size: 1, max_overflow: 5] |
Poolboy options for publishing. Controls the amount of connections established with the adapter service. |
For more information about configuration using OS environment variables check the module
Yggdrasil.Config
.
Installation
Yggdrasil
is available as a Hex package. To install, add it to your
dependencies in your mix.exs
file:
-
For Elixir < 1.8 and Erlang < 21
def deps do [{:yggdrasil, "~> 4.1"}] end
-
For Elixir ≥ 1.8 and Elixir < 1.12 and Erlang ≥ 21 and Erlang < 23
def deps do [{:yggdrasil, "~> 5.0"}] end
-
For Elixir Elixir ≥ 1.12 and Erlang ≥ 23
def deps do [{:yggdrasil, "~> 6.0"}] end
Author
Alexander de Sousa.
License
Yggdrasil
is released under the MIT License. See the LICENSE file for further
details.