yggdrasil v2.0.0 Yggdrasil

Yggdrasil is an immense mythical tree that connects the nine worlds in Norse cosmology.

Yggdrasil manages subscriptions to channels/queues in several brokers with the possibility to add more. Simple Redis, RabbitMQ and Postgres adapters are implemented. Message passing is done through YProcess. YProcess allows to use Phoenix.PubPub as a pub/sub to distribute messages between processes.

Example using Redis

iex(1)> channel = %Yggdrasil.Channel{channel: "redis_channel",
...(1)>                              decoder: Yggdrasil.Decoder.Default.Redis}
iex(2)> Yggdrasil.subscribe(channel)

Then in redis:

127.0.0.1:6379> PUBLISH "redis_channel" "hello"
(integer) (1)

And finally if you flush in your iex you’ll see the message received by the Elixir shell:

iex(3> flush()
{:Y_CAST_EVENT, "redis_channel", "hello"}
:ok

Things to note:

  • Every message coming from a broker (Redis, RabbitMQ, Postgres) will be like:
  {:Y_CAST_EVENT, channel, message}

Example using <code class="inline">GenServer</code>

The previous example can be wrapped inside a GenServer

defmodule Subscriber do
  use GenServer

  ###################
  # Client functions.

  def start_link(channel, opts \ []) do
    GenServer.start_link(__MODULE__, channel, opts)
  end

  def stop(subscriber, reason \ :normal) do
    GenServer.stop(subscriber, reason)
  end

  ######################
  # GenServer callbacks.

  def init(channel) do
    Yggdrasil.subscribe(channel)
    {:ok, channel}
  end

  def handle_info({:Y_CAST_EVENT, channel, message}, state) do
    IO.inspect %{channel: channel, message: message}
    {:noreply, state}
  end

  def terminate(_reason, channel) do
    Yggdrasil.unsubscribe(channel)
    :ok
  end
end
````

So in `iex`:

iex(1)> channel = %Yggdrasil.Channel{channel: “redis_channel”, …(1)> decoder: Yggdrasil.Decoder.Default.Redis} iex(2)> {:ok, subscriber} = Subscriber.start_link(channel) iex(3)>


Again in Redis:

127.0.0.1:6379> PUBLISH “redis_channel” “hello” (integer) (1)


And finally you'll see in your `iex` the following:

%{channel: “redis_channel”, message: “hello”} iex(3)>


## Example using `YProcess`

`YProcess` is a `GenServer` wrapper with pubsub capabilities and it has
great sinergy with [`Yggdrasil`](Yggdrasil.html#content). The previous example implemented with
`YProcess` would be:

defmodule YSubscriber do use YProcess, backend: Yggdrasil.Backend

################### # Client functions.

def start_link(channel, opts \ []) do

YProcess.start_link(__MODULE__, channel, opts)

end

def stop(subscriber, reason \ :normal) do

YProcess.stop(subscriber, reason)

end

##################### # YProcess callbacks.

def init(channel) do

{:join, [channel], channel}

end

def handle_event(channel, message, state) do

IO.inspect %{channel: channel, message: message}
{:noreply, state}

end end


So in `iex`:

iex(1)> channel = %Yggdrasil.Channel{channel: “redis_channel”, …(1)> decoder: Yggdrasil.Decoder.Default.Redis} iex(2)> {:ok, y_subscriber} = YSubscriber.start_link(channel) iex(3)>


Again in Redis:

127.0.0.1:6379> PUBLISH “redis_channel” “hello” (integer) (1)


And finally you'll see in your `iex` the following:

%{channel: “redis_channel”, message: “hello”} iex(3)>


## Yggdrasil Channels

[`Yggdrasil`](Yggdrasil.html#content) channels have the name of the channel in the broker and the
name of the module of the message decoder. A decoder module also defines
which adapter should be used to connect to the channel.

%Yggdrasil.Channel{channel: “channel”, decoder: Yggdrasil.Decoder.Default.Redis}

The previous example will tell Yggdrasil to subscribe to the channel `"channel"`.
The decoder module [`Yggdrasil.Decoder.Default.Redis`](Yggdrasil.Decoder.Default.Redis.html) defined Redis as the broker
and does not change the message coming from Redis before sending it to the
subscribers.

### Decoders

The current [`Yggdrasil`](Yggdrasil.html#content) version has the following decoder modules:

  * [`Yggdrasil.Decoder.Default`](Yggdrasil.Decoder.Default.html): Does nothing to the message and uses the
  [`Yggdrasil.Adapter.Elixir`](Yggdrasil.Adapter.Elixir.html).
  * [`Yggdrasil.Decoder.Default.Redis`](Yggdrasil.Decoder.Default.Redis.html): Does nothing to the message and uses the
  [`Yggdrasil.Adapter.Redis`](Yggdrasil.Adapter.Redis.html).
  * `Yggdrasil.Decoder.Default.RebbitMQ`: Does nothing to the message and uses the
  [`Yggdrasil.Adapter.RabbitMQ`](Yggdrasil.Adapter.RabbitMQ.html).
  * [`Yggdrasil.Decoder.Default.Postgres`](Yggdrasil.Decoder.Default.Postgres.html): Does nothing to the message and uses the
  [`Yggdrasil.Adapter.Postgres`](Yggdrasil.Adapter.Postgres.html).

> For more information about adapters, see the Adapters section.

To implement a decoder is necessary to implement the `decode/2` callback for
[`Yggdrasil.Decoder`](Yggdrasil.Decoder.html) behaviour, i.e. subscribe to a Redis channel `"test"` that
publishes JSON. The subscribers must receive a map instead of a string with the
JSON.

defmodule CustomDecoder do use Yggdrasil.Decoder, adapter: Yggdrasil.Adapter.Redis

def decode(_channel, message) do

Poison.decode!(message)

end end


To subscribe to this channel, clients must use the following [`Yggdrasil`](Yggdrasil.html#content) channel:

%Yggdrasil.Channel{channel: “test”, decoder: CustomDecoder}


### Adapters

The current [`Yggdrasil`](Yggdrasil.html#content) version has the following adapters:

  * [`Yggdrasil.Adapter.Elixir`](Yggdrasil.Adapter.Elixir.html): Message distribution using Elixir messages.
  * [`Yggdrasil.Adapter.Redis`](Yggdrasil.Adapter.Redis.html): Messages come from a Redis channel.
  * [`Yggdrasil.Adapter.RabbitMQ`](Yggdrasil.Adapter.RabbitMQ.html): Messages come from a RabbitMQ queue. A
  `channel` is a tuple that contains the exchange and the routing key:
  `{exchange, routing_key}`.
  * [`Yggdrasil.Adapter.Postgres`](Yggdrasil.Adapter.Postgres.html): Messages come from the notifies of a
  PostgreSQL database.

Also the function [`Yggdrasil.publish/2`](Yggdrasil.html#publish/2) is used to simulate published messages
by any of the brokers.

To implement a new adapter is necessary to use a `GenServer` or any wrapper over
`GenServer`. For more information, see the source code of any of the implemented
adapters.

## Installation

[`Yggdrasil`](Yggdrasil.html#content) is available as a Hex package. To install, add it to your
dependencies in your `mix.exs` file:

def deps do [{:yggdrasil, "~> 2.0.0"}] end


and ensure [`Yggdrasil`](Yggdrasil.html#content) is started before your application:

def application do [applications: [:yggdrasil]] end


## Configuration

[`Yggdrasil`](Yggdrasil.html#content) uses `YProcess` as a means to distribute the messages. So it is
necessary to provide a configuration for `YProcess` if you want to use, for
example, `Phoenix.PubSub` as your pubsub, i.e:

use Mix.Config

config :y_process, backend: YProcess.Backend.PhoenixPubSub, name: Yggdrasil.PubSub, adapter: Phoenix.PubSub.PG2, options: [pool_size: 10]

by default, `YProcess` will use `YProcess.Backend.PG2` as default backend.

For [`Yggdrasil`](Yggdrasil.html#content), there's only one general configuration parameter which is
the process name registry. By default, it uses `ExReg`, which is a simple but
rich process name registry. It is possible to use another one like `:gproc`.

use Mix.Config

config :yggdrasil, registry: :gproc


Specific configuration parameters are as follows:

  * To configure [`Yggdrasil`](Yggdrasil.html#content) with the provided Redis adapter
  ([`Yggdrasil.Adapter.Redis`](Yggdrasil.Adapter.Redis.html)):

use Mix.Config

config :yggdrasil,

redis: [host: "localhost",
        port: 6379,
        password: "my password"]
  The default Redis adapter uses `Redix`, so the configuration parameters have
  the same name as the ones in `Redix`. By default connects to `redis://localhost`.

  * To configure [`Yggdrasil`](Yggdrasil.html#content) with the provided RabbitMQ adapter
  ([`Yggdrasil.Adapter.RabbitMQ`](Yggdrasil.Adapter.RabbitMQ.html)):

use Mix.Config

config :yggdrasil,

rabbitmq: [host: "localhost",
          port: 5672,
          username: "guest",
          password: "guest",
          virtual_host: "/"]
  The default RabbitMQ adapter uses `AMQP`, so the configuration parameters have
  the same name as the ones in `AMQP`. By default connects to
  `amqp://guest:guest@localhost/`

  * To configure [`Yggdrasil`](Yggdrasil.html#content) with the provided PostgreSQL adapter
  ([`Yggdrasil.Adapter.Postgres`](Yggdrasil.Adapter.Postgres.html)):

use Mix.Config

postgres: [hostname: "localhost",
          port: 5432,
          username: "postgres",
          password: "postgres",
          database: "yggdrasil"]
  The default PostgreSQL adapter uses `Postgrex`, so the configuration parameters
  have the same name as the ones in `Postgrex`.

Summary

Functions

Emits a message in a channel. Bypasses the adapter

Subscribes to a channel

Unsubscribe from a channel

Functions

publish(channel, message)

Emits a message in a channel. Bypasses the adapter.

subscribe(channel)

Subscribes to a channel.

unsubscribe(channel)

Unsubscribe from a channel.