{"id":22048251,"url":"https://github.com/slavovojacek/rabbit_mq","last_synced_at":"2025-12-11T23:40:28.298Z","repository":{"id":48444667,"uuid":"212885908","full_name":"hqoss/rabbit_mq","owner":"hqoss","description":"🐇 Build consistent and well-balanced Producer/Consumer pipelines","archived":false,"fork":false,"pushed_at":"2021-07-26T06:22:44.000Z","size":1220,"stargazers_count":32,"open_issues_count":8,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-11-20T12:19:37.825Z","etag":null,"topics":["elixir","elixir-lang","elixir-library","rabbitmq","rabbitmq-best-practices","rabbitmq-modules"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hqoss.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-04T19:12:47.000Z","updated_at":"2022-07-15T04:42:36.000Z","dependencies_parsed_at":"2022-09-19T07:31:55.296Z","dependency_job_id":null,"html_url":"https://github.com/hqoss/rabbit_mq","commit_stats":null,"previous_names":["qworks-io/rabbitex"],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hqoss%2Frabbit_mq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hqoss%2Frabbit_mq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hqoss%2Frabbit_mq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hqoss%2Frabbit_mq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hqoss","download_url":"https://codeload.github.com/hqoss/rabbit_mq/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227351670,"owners_count":17768412,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","elixir-lang","elixir-library","rabbitmq","rabbitmq-best-practices","rabbitmq-modules"],"created_at":"2024-11-30T14:09:33.225Z","updated_at":"2025-12-11T23:40:23.249Z","avatar_url":"https://github.com/hqoss.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Elixir CI](https://github.com/hqoss/rabbit_mq/workflows/Elixir%20CI/badge.svg)\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/d8c50db737fe4b9bae614e2d06710443)](https://www.codacy.com/gh/hqoss/rabbit_mq?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=hqoss/rabbit_mq\u0026utm_campaign=Badge_Grade)\n[![Hex.pm](https://img.shields.io/hexpm/v/rabbit_mq.svg)](https://hex.pm/packages/rabbit_mq)\n[![Coverage Status](https://coveralls.io/repos/github/hqoss/rabbit_mq/badge.svg?branch=master)](https://coveralls.io/github/hqoss/rabbit_mq?branch=master)\n\n# 🐇 Elixir RabbitMQ Client\n\n`rabbit_mq` helps you build consistent and well-balanced Producer/Consumer pipelines.\n\n## Table of contents\n\n-   [Installation and Usage](#installation-and-usage)\n\n-   [Documentation](#documentation)\n\n-   [Sample usage](#sample-usage)\n\n    -   [Establish routing topology](#establish-routing-topology)\n\n    -   [Minimal configuration](#minimal-configuration)\n\n    -   [Producers](#producers)\n\n    -   [Consumers](#consumers)\n\n    -   [Start under supervision tree](#start-under-supervision-tree)\n\n        -   [Produce and Consume messages](#produce-and-consume-messages)\n\n-   [Advanced configuration](#advanced-configuration)\n\n    -   [Excessive logging](#excessive-logging)\n    -   [Lager conflicts with Elixir logger](#lager-conflicts-with-elixir-logger)\n\n-   [Testing](#testing)\n\n    -   [Producers](#producers-1)\n    -   [Consumers](#consumers-1)\n\n-   [Balanced performance and reliability](#balanced-performance-and-reliability)\n\n-   [TODO](#todo)\n\n## Installation and Usage\n\nAdd `:rabbit_mq` as a dependency to your project's `mix.exs`:\n\n```elixir\ndefp deps do\n  [\n    {:rabbit_mq, \"~\u003e 0.0.19\"}\n  ]\nend\n```\n\n## Documentation\n\nThe full documentation is [published on hex](https://hexdocs.pm/rabbit_mq/).\n\nThe following modules are provided;\n\n-   [`RabbitMQ.Topology`](https://hexdocs.pm/rabbit_mq/RabbitMQ.Topology.html)\n-   [`RabbitMQ.Connection`](https://hexdocs.pm/rabbit_mq/RabbitMQ.Connection.html)\n-   [`RabbitMQ.Consumer`](https://hexdocs.pm/rabbit_mq/RabbitMQ.Consumer.html)\n-   [`RabbitMQ.Producer`](https://hexdocs.pm/rabbit_mq/RabbitMQ.Producer.html)\n\n## Sample usage\n\n### Establish routing topology\n\n⚠️ All examples in this guide assume you've already set up your (RabbitMQ) routing topology as shown below.\n\n| source_name | source_kind | destination_name          | destination_kind | routing_key      | arguments |\n| ----------- | ----------- | ------------------------- | ---------------- | ---------------- | --------- |\n| customer    | exchange    | customer/customer.created | queue            | customer.created | \\[]       |\n| customer    | exchange    | customer/customer.updated | queue            | customer.updated | \\[]       |\n\nYou can run these commands against your `rabbitmq` instance to establish the desired routing topology.\n\n```bash\n# Declare the customer exchange\nrabbitmqadmin declare exchange name=customer type=topic durable=true\n\n# Declare and bind the customer/customer.created queue\nrabbitmqadmin declare queue name=customer/customer.created durable=true\nrabbitmqadmin declare binding source=customer destination=customer/customer.created routing_key=customer.created\n\n# Declare and bind the customer/customer.updated queue\nrabbitmqadmin declare queue name=customer/customer.updated durable=true\nrabbitmqadmin declare binding source=customer destination=customer/customer.updated routing_key=customer.updated\n```\n\nℹ️ You can also use the `RabbitMQ.Topology` module to quickly establish desired routing topology via your application.\n\nThis is what the result should look like in the RabbitMQ Management dashboard:\n\n![RabbitMQ Topology](assets/rabbitmq-topology.png)\n\n### Minimal configuration\n\nFirst, ensure you point to a valid `amqp_url` by configuring `:rabbit_mq` in your `config.exs`.\n\nℹ️ To run RabbitMQ locally, see our [docker-compose.yaml](docker-compose.yaml) for a sample Docker Compose set up.\n\n```elixir\nconfig :rabbit_mq, :amqp_url, \"amqp://guest:guest@localhost:5672\"\n```\n\nFor advanced configuration options, consult the [Configuration section](#advanced-configuration).\n\n### Producers\n\nLet's define our `CustomerProducer` first. We will use this module to publish messages onto the `\"customer\"` exchange.\n\n```elixir\ndefmodule RabbitSample.CustomerProducer do\n  @moduledoc \"\"\"\n  Publishes pre-configured events onto the \"customer\" exchange.\n  \"\"\"\n\n  use RabbitMQ.Producer, exchange: \"customer\", worker_count: 3\n\n  @doc \"\"\"\n  Publishes an event routed via \"customer.created\".\n  \"\"\"\n  def customer_created(customer_id) when is_binary(customer_id) do\n    opts = [\n      content_type: \"application/json\",\n      correlation_id: UUID.uuid4(),\n      mandatory: true\n    ]\n\n    data = Jason.encode!(%{v: \"1.0.0\", customer_id: customer_id})\n\n    publish(\"customer.created\", data, opts)\n  end\n\n  @doc \"\"\"\n  Publishes an event routed via \"customer.updated\".\n  \"\"\"\n  def customer_updated(updated_customer) when is_map(updated_customer) do\n    opts = [\n      content_type: \"application/json\",\n      correlation_id: UUID.uuid4(),\n      mandatory: true\n    ]\n\n    data = Jason.encode!(%{v: \"1.0.0\", customer_data: updated_customer})\n\n    publish(\"customer.updated\", data, opts)\n  end\nend\n```\n\n### Consumers\n\nTo consume messages off the respective queues, we will define 2 separate consumers.\n\n⚠️ Please note that automatic message acknowledgement is **disabled** in `rabbit_mq`, therefore it's _your_ responsibility to ensure messages are `ack`'d or `nack`'d.\n\nTo consume off `\"customer/customer.created\"`:\n\n```elixir\ndefmodule RabbitSample.CustomerCreatedConsumer do\n  use RabbitMQ.Consumer, queue: \"customer/customer.created\", worker_count: 2, prefetch_count: 3\n\n  require Logger\n\n  def handle_message(payload, meta, channel) do\n    %{delivery_tag: delivery_tag, redelivered: redelivered} = meta\n\n    try do\n      Logger.info(\"Customer created. Event data: #{payload}.\")\n      ack(channel, delivery_tag)\n    rescue\n      _ -\u003e nack(channel, delivery_tag, requeue: redelivered !== true)\n    end\n  end\nend\n```\n\nTo consume off `\"customer/customer.updated\"`:\n\n```elixir\ndefmodule RabbitSample.CustomerUpdatedConsumer do\n  use RabbitMQ.Consumer, queue: \"customer/customer.updated\", worker_count: 2, prefetch_count: 6\n\n  require Logger\n\n  def handle_message(payload, meta, channel) do\n    %{delivery_tag: delivery_tag, redelivered: redelivered} = meta\n\n    try do\n      Logger.info(\"Customer updated. Event data: #{payload}.\")\n      ack(channel, delivery_tag)\n    rescue\n      _ -\u003e nack(channel, delivery_tag, requeue: redelivered !== true)\n    end\n  end\nend\n```\n\n### Start under supervision tree\n\nAnd finally, we will start our application.\n\n```elixir\ndefmodule RabbitSample.Application do\n  use Application\n\n  def start(_type, _args) do\n    children = [\n      RabbitSample.CustomerProducer,\n      RabbitSample.CustomerCreatedConsumer,\n      RabbitSample.CustomerUpdatedConsumer\n    ]\n\n    opts = [strategy: :one_for_one, name: RabbitSample.Supervisor]\n    Supervisor.start_link(children, opts)\n  end\nend\n```\n\nUsing `iex`;\n\n```bash\niex -S mix\n```\n\nThe resulting application topology should look like this:\n\n![Application Topology](assets/application-topology.png)\n\nUpon closer inspection using the RabbitMQ Management dashboard, we see that:\n\n-   a) each of our modules maintains its dedicated connection; and\n-   b) each of our modules' workers maintains its dedicated channel under the respective connection.\n\n![Connections](assets/rabbitmq-connections.png)\n\nℹ️ Detailed view of how individual workers have set up their channels. Note that the **different prefetch counts** correspond to the different configuration we provided in our Consumers, and that the Producer's 3 worker channels operate in **Confirm mode**.\n\n![Channels](assets/rabbitmq-channels.png)\n\n#### Produce and Consume messages\n\n⚠️ Due to the asynchronous nature of the application, the order of outputs in the console may vary.\n\n```elixir\niex(1)\u003e RabbitSample.CustomerProducer.customer_created(UUID.uuid4())\n{:ok, 1}\n\n21:52:55.098 [debug] Received ACK of 1.\n\n21:52:55.098 [info]  Customer created. Event data: {\"customer_id\":\"b6712186-43be-46ce-a7b2-a4c4ab42efe7\",\"v\":\"1.0.0\"}.\n\niex(2)\u003e RabbitSample.CustomerProducer.customer_updated(%{id: UUID.uuid4()})\n{:ok, 1}\n\n21:53:06.918 [debug] Received ACK of 1.\n\n21:53:06.918 [info]  Customer updated. Event data: {\"customer_data\":{\"id\":\"e83e92c9-1915-4e9c-85bb-bf78b056fd76\"},\"v\":\"1.0.0\"}.\n\niex(3)\u003e\n```\n\n## Advanced configuration\n\nThe following options can be configured.\n\n```elixir\nconfig :rabbit_mq,\n  amqp_url: \"amqp://guest:guest@localhost:5672\",\n  heartbeat_interval_sec: 60,\n  reconnect_interval_ms: 2500,\n  max_channels_per_connection: 16\n```\n\n-   `amqp_url`; **required**, the broker URL.\n-   `heartbeat_interval_sec`; defines after what period of time the peer TCP connection should be considered unreachable. Defaults to `30`.\n-   `reconnect_interval_ms`; the interval before another attempt to re-connect to the broker should occur. Defaults to `2500`.\n-   `max_channels_per_connection`; maximum number of channels per connection. Also determines the maximum number of workers per Producer/Consumer module. Defaults to `8`.\n\n⚠️ Please consult the following guides to understand how to best configure `:max_channels_per_connection` and `:heartbeat_interval_sec` respectively.\n\n-   [Channels Resource Usage](https://www.rabbitmq.com/channels.html#resource-usage)\n-   [Detecting Dead TCP Connections with Heartbeats and TCP Keepalives](https://www.rabbitmq.com/heartbeats.html)\n\n### Excessive logging\n\nSee [original section in `amqp` docs](https://github.com/pma/amqp#log-related-to-amqp-supervisors-are-too-verbose).\n\nAdd the following configuration.\n\n```elixir\nconfig :logger, handle_otp_reports: false\n```\n\n### Lager conflicts with Elixir logger\n\nLager is used by `rabbit_common` and is not Elixir's best friend yet. You need a workaround.\n\n⚠️ In `mix.exs`, you have to load `:lager` before `:logger`.\n\n```elixir\n  extra_applications: [:lager, :logger]\n```\n\n## Testing\n\nThe library itself has been rigorously tested, so you should ideally only need to test whether you've configured your modules correctly.\n\nAdditionally, you _should_ test any side-effects driven by your Producers or Consumers.\n\n### Producers\n\nHere is a few ideas on how you can test your Producers.\n\n⚠️ The below snippet assumes your application starts the `CustomerProducer` module as shown in earlier examples.\n\n```elixir\ndefmodule RabbitSampleTest.CustomerProducer do\n  alias AMQP.{Basic, Channel, Connection, Queue}\n  alias RabbitSample.CustomerProducer\n\n  use ExUnit.Case\n\n  @amqp_url Application.get_env(:rabbit_mq, :amqp_url)\n  @exchange \"customer\"\n\n  setup_all do\n    assert {:ok, connection} = Connection.open(@amqp_url)\n    assert {:ok, channel} = Channel.open(connection)\n\n    # Declare an exclusive queue and bind it to the customer exchange.\n    {:ok, %{queue: queue}} = Queue.declare(channel, \"\", exclusive: true)\n    :ok = Queue.bind(channel, queue, @exchange, routing_key: \"#\")\n\n    # Clean up after all tests have ran.\n    on_exit(fn -\u003e\n      # This queue would have been deleted automatically when the connection\n      # gets closed, however we prefer to be explicit. Also, we ensure there\n      # are no messages left hanging in the queue.\n      assert {:ok, %{message_count: 0}} = Queue.delete(channel, queue)\n\n      assert :ok = Channel.close(channel)\n      assert :ok = Connection.close(connection)\n    end)\n\n    [channel: channel, queue: queue]\n  end\n\n  setup %{channel: channel, queue: queue} do\n    # Each test will be notified when a message is consumed.\n    assert {:ok, consumer_tag} = Basic.consume(channel, queue)\n\n    # This will always be the first message received by the process.\n    assert_receive({:basic_consume_ok, %{consumer_tag: ^consumer_tag}})\n\n    on_exit(fn -\u003e\n      # Ensure there are no messages in the queue as the next test is about to start.\n      assert true = Queue.empty?(channel, queue)\n    end)\n\n    [\n      channel: channel,\n      consumer_tag: consumer_tag\n    ]\n  end\n\n  describe \"#{__MODULE__}\" do\n    test \"defines correctly configured child specification\", %{\n      channel: channel,\n      consumer_tag: consumer_tag\n    } do\n      assert %{\n               id: CustomerProducer,\n               restart: :permanent,\n               shutdown: :brutal_kill,\n               start:\n                 {RabbitMQ.Producer, :start_link,\n                  [\n                    %{confirm_type: :async, exchange: @exchange, worker_count: 3},\n                    [name: CustomerProducer]\n                  ]},\n               type: :supervisor\n             } = CustomerProducer.child_spec([])\n\n      Basic.cancel(channel, consumer_tag)\n\n      # This will always be the last message received by the process.\n      assert_receive({:basic_cancel_ok, %{consumer_tag: ^consumer_tag}})\n\n      # Ensure no further messages are received.\n      refute_receive(_)\n    end\n\n    test \"customer_created/1 publishes correctly configured events\", %{\n      channel: channel,\n      consumer_tag: consumer_tag\n    } do\n      customer_id = UUID.uuid4()\n      expected_payload = Jason.encode!(%{v: \"1.0.0\", customer_id: customer_id})\n\n      assert {:ok, _seq_no} = CustomerProducer.customer_created(customer_id)\n\n      assert_receive(\n        {:basic_deliver, ^expected_payload,\n         %{\n           consumer_tag: ^consumer_tag,\n           content_type: \"application/json\",\n           correlation_id: correlation_id,\n           delivery_tag: delivery_tag,\n           routing_key: \"customer.created\"\n         }}\n      )\n\n      # Ensure correlation_id is a valid UUID.\n      assert {:ok, _} = UUID.info(correlation_id)\n\n      # Acknowledge that the message has been received.\n      Basic.ack(channel, delivery_tag)\n\n      # Stop consuming.\n      Basic.cancel(channel, consumer_tag)\n\n      # This will always be the last message received by the process.\n      assert_receive({:basic_cancel_ok, %{consumer_tag: ^consumer_tag}})\n\n      # Ensure no further messages are received.\n      refute_receive(_)\n    end\n\n    test \"customer_updated/1 publishes correctly configured events\", %{\n      channel: channel,\n      consumer_tag: consumer_tag\n    } do\n      customer_data = %{id: UUID.uuid4()}\n      expected_payload = Jason.encode!(%{v: \"1.0.0\", customer_data: customer_data})\n\n      assert {:ok, _seq_no} = CustomerProducer.customer_updated(customer_data)\n\n      assert_receive(\n        {:basic_deliver, ^expected_payload,\n         %{\n           consumer_tag: ^consumer_tag,\n           content_type: \"application/json\",\n           correlation_id: correlation_id,\n           delivery_tag: delivery_tag,\n           routing_key: \"customer.updated\"\n         }}\n      )\n\n      # Ensure correlation_id is a valid UUID.\n      assert {:ok, _} = UUID.info(correlation_id)\n\n      # Acknowledge that the message has been received.\n      Basic.ack(channel, delivery_tag)\n\n      # Stop consuming.\n      Basic.cancel(channel, consumer_tag)\n\n      # This will always be the last message received by the process.\n      assert_receive({:basic_cancel_ok, %{consumer_tag: ^consumer_tag}})\n\n      # Ensure no further messages are received.\n      refute_receive(_)\n    end\n  end\nend\n```\n\n### Consumers\n\nHere is a few ideas on how you can test your Consumers.\n\n⚠️ The below snippet assumes your application starts the `CustomerCreatedConsumer` module as shown in earlier examples.\n\n```elixir\ndefmodule RabbitSampleTest.CustomerCreatedConsumer do\n  alias AMQP.{Basic, Channel, Connection, Queue}\n  alias RabbitSample.CustomerCreatedConsumer\n\n  import ExUnit.CaptureLog\n\n  use ExUnit.Case\n\n  @amqp_url Application.get_env(:rabbit_mq, :amqp_url)\n  @exchange \"customer\"\n  @queue \"#{@exchange}/customer.created\"\n\n  setup_all do\n    assert {:ok, connection} = Connection.open(@amqp_url)\n    assert {:ok, channel} = Channel.open(connection)\n\n    # Declare an exclusive queue and bind it to the customer exchange.\n    {:ok, %{queue: queue}} = Queue.declare(channel, \"\", exclusive: true)\n    :ok = Queue.bind(channel, queue, @exchange, routing_key: \"#\")\n\n    # Clean up after all tests have ran.\n    on_exit(fn -\u003e\n      # This queue would have been deleted automatically when the connection\n      # gets closed, however we prefer to be explicit. Also, we ensure there\n      # are no messages left hanging in the queue.\n      assert {:ok, %{message_count: 0}} = Queue.delete(channel, queue)\n\n      assert :ok = Channel.close(channel)\n      assert :ok = Connection.close(connection)\n    end)\n\n    [channel: channel, queue: queue]\n  end\n\n  setup %{channel: channel, queue: queue} do\n    # Each test will be notified when a message is consumed.\n    assert {:ok, consumer_tag} = Basic.consume(channel, queue)\n\n    # This will always be the first message received by the process.\n    assert_receive({:basic_consume_ok, %{consumer_tag: ^consumer_tag}})\n\n    on_exit(fn -\u003e\n      # Ensure there are no messages in the queue as the next test is about to start.\n      assert true = Queue.empty?(channel, queue)\n    end)\n\n    [\n      channel: channel,\n      consumer_tag: consumer_tag\n    ]\n  end\n\n  describe \"#{__MODULE__}\" do\n    test \"defines correctly configured child specification\", %{\n      channel: channel,\n      consumer_tag: consumer_tag\n    } do\n      assert %{\n               id: CustomerCreatedConsumer,\n               restart: :permanent,\n               shutdown: :brutal_kill,\n               start:\n                 {RabbitMQ.Consumer, :start_link,\n                  [\n                    %{consume_cb: _, prefetch_count: 3, queue: @queue, worker_count: 2},\n                    [name: CustomerCreatedConsumer]\n                  ]},\n               type: :supervisor\n             } = CustomerCreatedConsumer.child_spec([])\n\n      # Stop consuming.\n      Basic.cancel(channel, consumer_tag)\n\n      # This will always be the last message received by the process.\n      assert_receive({:basic_cancel_ok, %{consumer_tag: ^consumer_tag}})\n\n      # Ensure no further messages are received.\n      refute_receive(_)\n    end\n\n    test \"handle_message/3 logs a message\", %{\n      channel: channel,\n      consumer_tag: consumer_tag\n    } do\n      correlation_id = UUID.uuid4()\n      payload = Jason.encode!(%{v: \"1.0.0\", customer_id: UUID.uuid4()})\n\n      Basic.publish(channel, @exchange, \"customer.created\", payload,\n        correlation_id: correlation_id\n      )\n\n      assert_receive(\n        {:basic_deliver, payload,\n         %{\n           consumer_tag: ^consumer_tag,\n           correlation_id: ^correlation_id,\n           routing_key: \"customer.created\"\n         } = meta}\n      )\n\n      assert capture_log(fn -\u003e\n               CustomerCreatedConsumer.handle_message(payload, meta, channel)\n             end) =~ \"Customer #{payload} created\"\n\n      # Stop consuming.\n      Basic.cancel(channel, consumer_tag)\n\n      # This will always be the last message received by the process.\n      assert_receive({:basic_cancel_ok, %{consumer_tag: ^consumer_tag}})\n\n      # Ensure no further messages are received.\n      refute_receive(_)\n    end\n  end\nend\n```\n\n⚠️ The below snippet assumes your application starts the `CustomerUpdatedConsumer` module as shown in earlier examples.\n\n```elixir\ndefmodule RabbitSampleTest.CustomerUpdatedConsumer do\n  alias AMQP.{Basic, Channel, Connection, Queue}\n  alias RabbitSample.CustomerUpdatedConsumer\n\n  import ExUnit.CaptureLog\n\n  use ExUnit.Case\n\n  @amqp_url Application.get_env(:rabbit_mq, :amqp_url)\n  @exchange \"customer\"\n  @queue \"#{@exchange}/customer.updated\"\n\n  setup_all do\n    assert {:ok, connection} = Connection.open(@amqp_url)\n    assert {:ok, channel} = Channel.open(connection)\n\n    # Declare an exclusive queue and bind it to the customer exchange.\n    {:ok, %{queue: queue}} = Queue.declare(channel, \"\", exclusive: true)\n    :ok = Queue.bind(channel, queue, @exchange, routing_key: \"#\")\n\n    # Clean up after all tests have ran.\n    on_exit(fn -\u003e\n      # This queue would have been deleted automatically when the connection\n      # gets closed, however we prefer to be explicit. Also, we ensure there\n      # are no messages left hanging in the queue.\n      assert {:ok, %{message_count: 0}} = Queue.delete(channel, queue)\n\n      assert :ok = Channel.close(channel)\n      assert :ok = Connection.close(connection)\n    end)\n\n    [channel: channel, queue: queue]\n  end\n\n  setup %{channel: channel, queue: queue} do\n    # Each test will be notified when a message is consumed.\n    assert {:ok, consumer_tag} = Basic.consume(channel, queue)\n\n    # This will always be the first message received by the process.\n    assert_receive({:basic_consume_ok, %{consumer_tag: ^consumer_tag}})\n\n    on_exit(fn -\u003e\n      # Ensure there are no messages in the queue as the next test is about to start.\n      assert true = Queue.empty?(channel, queue)\n    end)\n\n    [\n      channel: channel,\n      consumer_tag: consumer_tag\n    ]\n  end\n\n  describe \"#{__MODULE__}\" do\n    test \"defines correctly configured child specification\", %{\n      channel: channel,\n      consumer_tag: consumer_tag\n    } do\n      assert %{\n               id: CustomerUpdatedConsumer,\n               restart: :permanent,\n               shutdown: :brutal_kill,\n               start:\n                 {RabbitMQ.Consumer, :start_link,\n                  [\n                    %{consume_cb: _, prefetch_count: 6, queue: @queue, worker_count: 2},\n                    [name: CustomerUpdatedConsumer]\n                  ]},\n               type: :supervisor\n             } = CustomerUpdatedConsumer.child_spec([])\n\n      # Stop consuming.\n      Basic.cancel(channel, consumer_tag)\n\n      # This will always be the last message received by the process.\n      assert_receive({:basic_cancel_ok, %{consumer_tag: ^consumer_tag}})\n\n      # Ensure no further messages are received.\n      refute_receive(_)\n    end\n\n    test \"handle_message/3 logs a message\", %{\n      channel: channel,\n      consumer_tag: consumer_tag\n    } do\n      correlation_id = UUID.uuid4()\n      customer_data = %{id: UUID.uuid4()}\n      payload = Jason.encode!(%{v: \"1.0.0\", customer_data: customer_data})\n\n      Basic.publish(channel, @exchange, \"customer.updated\", payload,\n        correlation_id: correlation_id\n      )\n\n      assert_receive(\n        {:basic_deliver, payload,\n         %{\n           consumer_tag: ^consumer_tag,\n           correlation_id: ^correlation_id,\n           routing_key: \"customer.updated\"\n         } = meta}\n      )\n\n      assert capture_log(fn -\u003e\n               CustomerUpdatedConsumer.handle_message(payload, meta, channel)\n             end) =~ \"Customer updated. Data: #{payload}.\"\n\n      # Stop consuming.\n      Basic.cancel(channel, consumer_tag)\n\n      # This will always be the last message received by the process.\n      assert_receive({:basic_cancel_ok, %{consumer_tag: ^consumer_tag}})\n\n      # Ensure no further messages are received.\n      refute_receive(_)\n    end\n  end\nend\n```\n\n## Balanced performance and reliability\n\nThe RabbitMQ modules are pre-configured with sensible defaults and follow design principles that improve and delicately balance both performance _and_ reliability.\n\nThis has been possible through\n\n-   a) extensive experience of working with Elixir and RabbitMQ in production; _and_\n-   b) meticulous consultation of the below (and more) documents and guides.\n\n⚠️ While most of the heavy-lifting is provided by the library itself, reading through the documents below before running _any_ application in production is thoroughly recommended.\n\n-   [Connections](https://www.rabbitmq.com/connections.html)\n-   [Channels](https://www.rabbitmq.com/channels.html)\n-   [Reliability Guide](https://www.rabbitmq.com/reliability.html)\n-   [Consumer Acknowledgements and Publisher Confirms](https://www.rabbitmq.com/confirms.html)\n-   [Consumer Acknowledgement Modes and Data Safety Considerations](https://www.rabbitmq.com/confirms.html#acknowledgement-modes)\n-   [Reliable publishing with publisher confirms](https://www.rabbitmq.com/tutorials/tutorial-seven-java.html)\n-   [Consumer Prefetch](https://www.rabbitmq.com/consumer-prefetch.html)\n-   [Production Checklist](https://www.rabbitmq.com/production-checklist.html)\n-   [RabbitMQ Best Practices](https://www.cloudamqp.com/blog/2017-12-29-part1-rabbitmq-best-practice.html)\n-   [RabbitMQ Best Practice for High Performance (High Throughput)](https://www.cloudamqp.com/blog/2018-01-08-part2-rabbitmq-best-practice-for-high-performance.html)\n\n## TODO\n\nA quick and dirty tech-debt tracker, used in conjunction with Issues.\n\n-   [ ] Add support for individual and batch publisher confirms.\n-   [ ] Add support for publishing without confirm mode.\n-   [ ] Increase test coverage to as close to 100% as possible.\n-   [ ] Update testing guide.\n-   [ ] Expose `get_connection` on the individual Consumer and Producer level\n-   [ ] Change `handle_message/3` to return `:ok | {:error, :requeue} | {:error, term()}`\n-   [ ] Add `handle_publisher_ack`, make both optional\n-   [ ] Add all optional callbacks (`:basic_cancel`, etc.) to the Consumer module\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslavovojacek%2Frabbit_mq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fslavovojacek%2Frabbit_mq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslavovojacek%2Frabbit_mq/lists"}