The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Net::Stomp::Producer - helper object to send messages via Net::Stomp

VERSION

version 2.005

SYNOPSIS

  my $ser = JSON::XS->new->utf8;

  my $p = Net::Stomp::Producer->new({
    connect_headers => { login => 'some-login', passcode => 's3cr3t' },
    servers => [
      { hostname => 'broker1.local', port => 61613 },
      { hostname => 'broker2.local', port => 61613, ssl => 1 },
      { hostname => 'broker3.local', port => 61613, ssl => 1,
        connect_headers => { login => 'some-different-login',
                             passcode => 'an0th3r-s3cr3t' },
      },
    ],
    serializer => sub { $ser->encode($_[0]) },
    default_headers => { 'content-type' => 'json' },
  });

  $p->send('/queue/somewhere',
           { type => 'my_message' },
           { a => [ 'data', 'structure' ] });

Also:

  package My::Message::Transformer {
    use Moose;
    sub transform {
      my ($self,@elems) = @_;

      return { destination => '/queue/somewhere',
               type => 'my_message', },
             { a => \@elems };
    }
  }

  $p->transform_and_send('My::Message::Transformer',
                         'data','structure');

Or even:

  my $t = My::Message::Transformer->new();
  $p->transform_and_send($t,
                         'data','structure');

They all send the same message.

DESCRIPTION

This class sends messages via a STOMP connection (see Net::Stomp::MooseHelpers::CanConnect). It provides facilities for serialisation and validation. You can have an instance of this class as a singleton / global in your process, and use it to send all your messages: this is recommended, as it will prevent flooding the broker with many connections (each instance would connect independently, and if you create many instances per second, the broker or your process may run out of file descriptiors and stop working).

You can use it at several levels:

Raw sending

  my $p = Net::Stomp::Producer->new({
    servers => [ { hostname => 'localhost', port => 61613 } ],
  });

  $p->send($destination,\%headers,$body_byte_string);

This will just wrap the parameters in a Net::Stomp::Frame and send it. $destination can be undef, if you have set it in the %headers.

Serialisation support

  my $p = Net::Stomp::Producer->new({
    servers => [ { hostname => 'localhost', port => 61613 } ],
    serializer => sub { encode_json($_[0]) },
  });

  $p->send($destination,\%headers,$body_hashref);

The body will be passed through the serializer, and the resulting string will be used as above.

Transformer instance

  $p->transform_and_send($transformer_obj,@args);

This will call $transformer_obj->transform(@args). That function should return a list (with an even number of elements). Each pair of elements is interpreted as \%headers, $body_ref and passed to "send" as above (with no destination, so the transformer should set it in the headers). It's not an error for the transformer to return an empty list: it just means that nothing will be sent.

Transformer class

  my $p = Net::Stomp::Producer->new({
    servers => [ { hostname => 'localhost', port => 61613 } ],
    transformer_args => { some => 'param' },
  });

  $p->transform_and_send($transformer_class,@args);

The transformer will be instantiated like $transformer_class->new($p->transformer_args), then the object will be called as above.

Transform & validate

If the transformer class / object supports the validate method, it will be called before sending each message, like:

  $transformer_obj->validate(\%headers,$body_ref);

This method is expected to return a true value if the message is valid, and throw a meaningful exception if it is not. The exception will be wrapped in a Net::Stomp::Producer::Exceptions::Invalid. If the validate method returns false without throwing any exception, Net::Stomp::Producer::Exceptions::Invalid will still be throw, but the previous_exception slot will be undef.

ATTRIBUTES

serializer

A coderef that, passed the body parameter from "send", returns a byte string to use as the frame body. The default coderef will just pass non-refs through, and die (with a Net::Stomp::Producer::Exceptions::CantSerialize exception) if passed a ref.

default_headers

Hashref of STOMP headers to use for every frame we send. Headers passed in to "send" take precedence. There is no support for removing a default header for a single send.

transactional_sending

DEPRECATED. Use "sending_method" instead. This boolean was too restrictive.

Instead of doing ->transactional_sending(1) do ->sending_method('transactional').

Instead of doing ->transactional_sending(0) do ->sending_method('') or ->sending_method('default').

Boolean, defaults to false. If true, use "send_transactional" in Net::Stomp instead of "send" in Net::Stomp to send frames.

sending_method

String, defaults to ''. Selects which method to use on the connection's Net::Stomp object to actually send a message. The name of the method is derived from the value of this attribute by prepending send_ to it (so you can't abuse this to call arbitrary methods), unless this attribute's value is '' or 'default', in which case the simple send method will be used.

For example, sending_method => 'with_receipt' will block sending until the broker sends back a receipt for the message (or it times out).

sending_method => 'transactional' will send a COMMIT frame when the receipt is received, or a ROLLBACK frame if something breaks.

NOTE: these methods work when the connection is used only to send messages, and not to receive them! The current implementation will very probably deadlock or throw exceptions at random moments if messages arrive while you're sending.

transformer_args

Hashref to pass to the transformer constructor when "make_transformer" instantiates a transformer class.

METHODS

send

  $p->send($destination,\%headers,$body);

Serializes the $body via the "serializer", merges the %headers with the "default_headers", setting the content-length to the byte length of the serialized body. Overrides the destination in the headers with $destination if it's defined.

Finally, sends the frame.

make_transformer

  $p->make_transformer($class);

If passed a reference, this function just returns it (it assumes it's a transformer object ready to use).

If passed a string, tries to load the class with Module::Runtime::use_package_optimistically. If the class has a new method, it's invoked with the value of "transformer_args" to obtain an object that is then returned. If the class does not have a new, the class name is returned.

transform

  my (@headers_and_bodies) = $p->transform($transformer,@data);

Uses "make_transformer" to (optionally) instantiate a transformer object, then tries to call transform on it. If there is no such method, a Net::Stomp::Producer::Exceptions::BadTransformer is thrown.

The transformer is expected to return a list of (header,body) pairs (that is, a list with an even number of elements; not a list of arrayrefs!).

Each message in the returned list is optionally validated, then returned.

The optional validation happens if the transformer ->can('validate'). If it can, that method is called like:

  $transformer->validate($header,$body_ref);

The method is expected to return a true value if the message is valid, and throw a meaningful exception if it is not. The exception will be wrapped in a Net::Stomp::Producer::Exceptions::Invalid. If the validate method returns false without throwing any exception, Net::Stomp::Producer::Exceptions::Invalid will still be throw, but the previous_exception slot will be undef.

It's not an error for the transformer to return an empty list: it just means that nothing will be returned.

send_many

  $p->send_many(@headers_and_bodies);

Given a list of (header,body) pairs (that is, a list with an even number of elements; not a list of arrayrefs!), it will send each pair as a message. Useful in combination with "transform".

It's not an error for the list to beempty: it just means that nothing will be sent.

transform_and_send

  $p->transform_and_send($transformer,@data);

Equivalent to:

  $p->send_many($p->transform($transformer,@data));

which is similar to:

  my ($header,$body) = $p->transform($transformer,@data);
  $p->send(undef,$header,$body);

but it works also when the transformer returns more than one pair.

It's not an error for the transformer to return an empty list: it just means that nothing will be sent.

Why would I ever want to use "transform" and "send_many" separately?

Let's say you are in a transaction, and you want to fail if the messages cannot be prepared, but not fail if the prepared messages cannot be sent. In this case, you call "transform" inside the transaction, and "send_many" outside of it.

But yes, in most cases you should really just call transform_and_send.

EXAMPLES

You can find examples of use in the tests, or at https://github.com/dakkar/CatalystX-StompSampleApps

AUTHOR

Gianni Ceccarelli <gianni.ceccarelli@net-a-porter.com>

COPYRIGHT AND LICENSE

This software is copyright (c) 2012 by Net-a-porter.com.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.