rabbit_mq v0.0.10 RabbitMQ.Producer behaviour

This module can be used to start and maintain a pool of Producer workers.

Example usage

rabbit_mq allows you to design consistent, SDK-like Producers.

ℹ️ The following example assumes that the "customer" exchange already exists.

First, define your (ideally domain-specific) Producer:

defmodule RabbitSample.CustomerProducer do
  @moduledoc """
  Publishes pre-configured events onto the "customer" exchange.
  """

  use RabbitMQ.Producer, exchange: "customer", worker_count: 3

  @doc """
  Publishes an event routed via "customer.created".
  """
  def customer_created(customer_id) when is_binary(customer_id) do
    opts = [
      content_type: "application/json",
      correlation_id: UUID.uuid4(),
      mandatory: true
    ]

    payload = Jason.encode!(%{v: "1.0.0", customer_id: customer_id})

    publish(payload, "customer.created", opts)
  end

  @doc """
  Publishes an event routed via "customer.updated".
  """
  def customer_updated(updated_customer) when is_map(updated_customer) do
    opts = [
      content_type: "application/json",
      correlation_id: UUID.uuid4(),
      mandatory: true
    ]

    payload = Jason.encode!(%{v: "1.0.0", customer_data: updated_customer})

    publish(payload, "customer.updated", opts)
  end

  @doc """
  In the unlikely event of a failed publisher confirm, messages that go
  unack'd will be passed onto this callback. You can use this to notify
  another process and deal with such exceptions in any way you like.
  """
  def on_unexpected_nack(unackd_messages) do
    Logger.error("Failed to publish messages: #{inspect(unackd_messages)}")
  end
end

Then, start as normal under your existing supervision tree:

children = [
  RabbitSample.Topology,
  RabbitSample.CustomerProducer,
  RabbitSample.CustomerCreatedConsumer,
  RabbitSample.CustomerUpdatedConsumer
]

opts = [strategy: :one_for_one, name: RabbitSample.Supervisor]
Supervisor.start_link(children, opts)

Finally, call the exposed methods from your application:

RabbitSample.CustomerProducer.customer_created(customer_id)
RabbitSample.CustomerProducer.customer_updated(updated_customer)

⚠️ Please note that all Producer workers implement "reliable publishing". Each Producer worker handles its publisher confirms asynchronously, striking a delicate balance between performance and reliability.

To understand why this is important, please refer to the reliable publishing implementation guide.

ℹ️ In the unlikely event of an unexpected Publisher nack, your server will be notified via the on_unexpected_nack/1 callback, letting you handle such exceptions in any way you see fit.

on_unexpected_nack/1 is a required callback with the following signature, and as such must be implemented by your Producer modules, even if it does nothing;

@type seq_no :: integer()
@type payload :: String.t()
@type routing_key :: String.t()
@type opts :: keyword()
@type original_publish_args :: {seq_no(), payload(), routing_key(), opts()}
@type unackd_messages :: list(original_publish_args())
@type result :: term()

@callback on_unexpected_nack(unackd_messages()) :: result()

Configuration

The following options can be used with RabbitMQ.Producer;

  • :confirm_type; publisher acknowledgement mode. Only :async is supported for now. Please consult the Publisher Confirms section for more details. Defaults to :async.
  • :exchange; the name of the exchange onto which the producer workers will publish. Required.
  • :worker_count; number of workers to be spawned. Cannot be greater than :max_channels_per_connection set in config. Defaults to 3.

When you use RabbitMQ.Consumer, a few things happen;

  1. The module turns into a GenServer.
  2. The server starts and supervises the desired number of workers.
  3. publish/3 becomes available in your module.

publish/3 is a private function with the following signature;

@type payload :: String.t()
@type routing_key :: String.t()
@type opts :: keyword()
@type result :: :ok | {:error, :correlation_id_missing}

publish(payload(), routing_key(), opts()) :: result()

⚠️ Please note that correlation_id is always required and failing to provide it will result in an exception.

ℹ️ To see which options can be passed as opts to publish/3, visit https://hexdocs.pm/amqp/AMQP.Basic.html#publish/5.

Link to this section Summary

Functions

Returns a specification to start this module under a supervisor.

Starts this module as a process via GenServer.start_link/3.

Invoked when the server is about to exit. It should do any cleanup required. See https://hexdocs.pm/elixir/GenServer.html#c:terminate/2 for more details.

Link to this section Types

Link to this type

opts()

opts() :: keyword()
Link to this type

original_publish_args()

original_publish_args() :: {seq_no(), payload(), routing_key(), opts()}
Link to this type

payload()

payload() :: String.t()
Link to this type

result()

result() :: term()
Link to this type

routing_key()

routing_key() :: String.t()
Link to this type

seq_no()

seq_no() :: integer()
Link to this type

unackd_messages()

unackd_messages() :: [original_publish_args()]

Link to this section Functions

Link to this function

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

Link to this function

start_link(config, opts)

start_link(map(), keyword()) :: GenServer.on_start()

Starts this module as a process via GenServer.start_link/3.

Only used by the module's child_spec.

Link to this function

terminate(reason, state)

Invoked when the server is about to exit. It should do any cleanup required. See https://hexdocs.pm/elixir/GenServer.html#c:terminate/2 for more details.

Link to this section Callbacks

Link to this callback

on_unexpected_nack(unackd_messages)

on_unexpected_nack(unackd_messages()) :: result()