Wring a RabbitMQ client in Elixir

1,094 views
Skip to first unread message

Chris Johnson

unread,
Jul 27, 2013, 1:20:32 PM7/27/13
to elixir-l...@googlegroups.com
Hi,

I'm trying to get a tiny Elixir app going that talks to my local RabbitMQ server.

This is what I'm after, only in Elixir instead of Erlang:

Anyone know of any existing elixir code that does anything with RabbitMQ? I know there are lots of examples of where Elixir is being used to write rabbit plugins, but I'm interested in the client side. I think I'm struggling because of differences in how includes, records, etc are referenced/defined in Elixir vs Erlang, and what specific ones are required for a client app.

Thanks in advance!

Chris

Chris Johnson

unread,
Jul 27, 2013, 2:00:53 PM7/27/13
to elixir-l...@googlegroups.com
I have to add that I'm a very new Elixir dev, so am trying to get my feet on the ground...

With that said, I've come across the following post, in Japanese/English, that appears to be an example of using a Github project called Erm:


defmodule Amqp do
  use Erm
  use Amqp.Uri
  Erm.addpath("dist/rabbit_common*/ebin")
  :io.format("code ~p~n", [:code.lib_dir(:rabbit_common)])
  Erm.defrecords_from_hrl("deps/**/amqp_client*/include/amqp_client.hrl")

  def start() do
    {:ok, re} = Amqp.Uri.parse("amqp://user:pas...@amqp-server-host.example.com")
    :io.format("Refields: ~p~n",
                 [Erm.record_info(:fields, :amqp_params_network)])
    :io.format("Re: ~p~n", [re])
    {:ok, con} = Amqp.Connection.start(re)
    {:ok, chan} = Amqp.Connection.open_channel(con)
    ex = Erm.record(:"exchange.declare", [exchange: "my_exchange",
                   type: "topic"])
    Erm.recordl(:"exchange.declare_ok") = Amqp.Channel.call(chan, ex)
    q = Erm.record(:"queue.declare", [queue: "my_queue"])

    Erm.recordl(:"queue.declare_ok") = Amqp.Channel.call(chan, q)
    binding = Erm.record(:"queue.bind", [queue: "my_queue",
                                         exchange: "my_exchange",
                                         routing_key: "key"])
    Erm.recordl(:"queue.bind_ok") = Amqp.Channel.call(chan, binding)

    payload = "foobar"
    publish = Erm.record(:"basic.publish", [exchange: "my_exchange",
                                            routing_key: "key"])
    p = Erm.record(:"P_basic", [delivery_mode: 2])
    p = Erm.record(:"P_basic", p, [delivery_mode: 2])
    Amqp.Channel.cast(chan, publish, Erm.record(:amqp_msg, [props: p,
                                                            payload: payload]))

    :timer.sleep(10000)
    get = Erm.record(:"basic.get", [queue: "my_queue", no_ack: true])
    {Erm.recordl(:"basic.get_ok"), content} = Amqp.Channel.call(chan, get)
    Erm.recordl(:amqp_msg, [payload: payload2]) = content
    :io.format("~p ~p", [payload, payload2])
    binding = Erm.record(:"queue.unbind", [queue: "my_queue",
                                          exchange: "my_exchange",
                                         routing_key: "key"])

    Erm.recordl(:"queue.unbind_ok") = Amqp.Channel.call(chan, binding)
    delete = Erm.record(:"exchange.delete", [exchange: "my_exchange"])
    Erm.recordl(:"exchange.delete_ok") = Amqp.Channel.call(chan, delete)
    delete = Erm.record(:"queue.delete", [queue: "my_queue"])
    Erm.recordl(:"queue.delete_ok") = Amqp.Channel.call(chan, delete)
    :ok = Amqp.Channel.close(chan)
    :ok = Amqp.Connection.close(con)
  end
end

Is it better to handle the management of records manually, or is using an abstraction like this a better approach? It seems like using RabbitMQ might require a TON of references that a project like this could eliminate (keep things DRY).

Chris

José Valim

unread,
Jul 27, 2013, 4:06:55 PM7/27/13
to elixir-l...@googlegroups.com
Hello Chris,

You could import the Erlang records and make use of them from Elixir-land:

defrecord :amqp_params_network, Record.extract(:amqp_params_network,
                                               from_lib: "amqp_client/include/amqp_client.hrl")
:amqp_params_network.new(host: "localhost")

However, there are a few limitations:

1. Records defined without an Elixir alias, like :amqp_params_network, are not pretty printed
2. If the record contains any function (or non serializable data type like pids), you won't be able to define them unless you set those fields to nil (which may not be accepted by ampq
3. Just a note: remember double quoted in Elixir is a binary and a double quoted in Erlang is a char list

You could try to go this way and it may work, if not, you can create a src/ampq_integration.erl file in your mix project that will expose an API to manipulate the records for you from Erlang, so you get the best of both words. Mix should automatically compile and load those erlang files for you.


José Valim
Skype: jv.ptec
Founder and Lead Developer



Chris

--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Chris Johnson

unread,
Jul 27, 2013, 4:56:57 PM7/27/13
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br
Thank you. 

I was aware of the limitations of records that contain functions; I ran across that last night.

This is what I have so far:

At the moment, when I try to start the AMQP connection, I'm getting an error:

** (MatchError) no match of right hand side value: {:error, {:bad_return_value, :ok}}
    :erl_eval.expr/3

** (EXIT from #PID<0.76.0>) {:bad_return_value, :ok}

I suspect the connection to the server/cluster (localhost) is failing for some reason; can't seem to discover why. I DID just try a connection to the same server/cluster via ruby, and it works. The ports are standard (5672, 5673).

As you can see from the code, I AM setting the :auth_mechanisms key to nil. I've tried using both the direct and network variations on the params hash/tuple. 

I'm concerned I haven't included the appropriate .hrl files as Record definitions up top. Is there a rule of thumb as to how you determine what you need to include as refs to these sorts of things when you integrate with an external lib like AMQP?

Thanks very much for your help!

Chris

José Valim

unread,
Jul 27, 2013, 4:59:05 PM7/27/13
to elixir-l...@googlegroups.com
Looking at the code, the only think I can think of is: have you tried using single quotes 'guest' for the password? A double quoted in Erlang is a char list (single quoted) in Elixir.


José Valim
Skype: jv.ptec
Founder and Lead Developer



Chris

--

Chris Johnson

unread,
Jul 27, 2013, 5:02:48 PM7/27/13
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br
No - same response.

When I inspect the record imported from Erlang, all the record attribs are double quoted too.

Here's what I'm getting back:

{:error, {:EXIT, {{:case_clause, ElixirAmqpConsumer.Server.AMQP.ParamsDirect[username: "guest", password: 'guest', virtual_host: "/", node: :"nonode@nohost", adapter_info: :none, client_properties: []]}, [{:amqp_connection_sup, :start_link, 1, []}, {:supervisor2, :do_start_child_i, 3, []}, {:supervisor2, :handle_call, 3, []}, {:gen_server, :handle_msg, 5, [file: 'gen_server.erl', line: 588]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 239]}]}}}

Does that help at all?

Chris

José Valim

unread,
Jul 27, 2013, 5:16:47 PM7/27/13
to elixir-l...@googlegroups.com
Ah, looking at the stacktrace you can see the error is happening here:


You defined your own record but the AMPQ Client is expecting its own record. To be 100% clear, this record:


Should be defrecord :ampq_params_network, otherwise the first element of the tuple won't match.


José Valim
Skype: jv.ptec
Founder and Lead Developer



Chris

--

Chris Johnson

unread,
Jul 27, 2013, 5:22:39 PM7/27/13
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br
W00T!!!! 

The start now works... ;-)

More work to do, but it's shaping up. Thanks a ton, José !!!

- Chris
Reply all
Reply to author
Forward
0 new messages