rebus-org / Rebus

:bus: Simple and lean service bus implementation for .NET
https://mookid.dk/category/rebus
Other
2.34k stars 364 forks source link

RabbitMq: Send to exchange instead of queue (when using exchange as queue's public address) #237

Closed pruiz closed 10 years ago

pruiz commented 10 years ago

Hi,

I am actually coding a few additions to my previous rabbitmq routing changes, and I've found a situation which maybe better to ask before proceeding.. ;)

When using an exchange as public address (instead of using a queue directly), I am facing problems with direct transmission (ie. when using Bus.Send) of messages to an endpoint.

As of now, rebus's rabbitmq driver assumes the "endpoint" defined at rebus' EndpointMapping configuration section to be a queue name, however, when an endpoint is using an exchange as it's public facing address, we need a way to define an EndpointMapping which points to an exchange instead of a queue.. But I am not sure of what should be the preferred option to achieve such a thing, and I see a few alternatives:

1) Using some form of syntax trick (like prefixing the string with an ! or an @, or enclosing it between <..>), so we can distinguish wether an endpoint address points to a queue or an exchange.

<add messages="Some.Messages" endpoint="IAmAQueue" /> <add messages="Some.Messages" endpoint="@IAmAnExchange" />

2) Adding a new property to EndpointMapping class so we can declare a type to each mapping, and let each transport implementation take care of what 'type' means.

<add messages="Some.Messages" type="queue" endpoint="IAmAQueue" /> <add messages="Some.Messages" type="exchange" endpoint="IAmAnExchange" />

3) Assume that if this endpoint it's configured to work by using an exchange as public address, all our routes to other endpoints point to exchanges aswell. That would mean mixing endpoints with exchange as public address with endpoint using only queues wont be possible. (I really dont like this approach, but I mention for completeness..)

Ideas, or opinions?

/cc @maeserichar

mookid8000 commented 10 years ago

It's getting late here, and I am a little bit tired - so maybe this is nonsense :)

But when Rebus publishes "directly" to a queue, it just publishes with a routing key that happens to be the same as a queue that is bound to a topic with the same name.

Could it be an option that - when you enable the "publish-to-exchange-feature" - you can either specify fully qualified names of exchange/topic pairs and or exchanges?

As in

<add messages="Some.Messages" type="queue" endpoint="IAmAQueue@SomeExchange" />
<add messages="Some.Messages" type="queue" endpoint="IAmAQueue@AnotherExchange" />
<add messages="Some.Messages" type="exchange" endpoint="IAmAnExchange" />

?

This would actually align well with the "magic" Rebus-MSMQ syntax of referring to a queue on another machine: a_queue@another_machine....

pruiz commented 10 years ago

I dont see any reason not to do it this way.. ;)

pruiz commented 10 years ago

The main issue I have with this change is IDetermineMessageOwnership, which is expected to return a string from GetEndpointFor(Type).

I can change this interface so it now returns a class or an struct with Type & Endpoint properties.. But this is going to be a breaking change for anyone relaying on IDetermineMessageOwnership.

Alternativelly I can go with a mix of the available ideas, and instead of adding a 'type' to MappingElement, simply make use of '@' at endpoint like:

<add messages="Some.Messages" endpoint="IAmAQueue" /> <add messages="Some.Messages" endpoint="@IAmAnExchange" /> <add messages="Some.Messages" endpoint="IAmAQueue@AtAnExchange" />

mookid8000 commented 10 years ago

Yes, that's it!

I can't see if I've missed something, but wouldn't it remove all special cases if "queue names" with RabbitMQ could always be specified like this:

someLogicalQueue
someLogicalQueue@anotherExchange
@yetAnotherExchange

both as the input queue name and as error queue name and in the endpoint mappings?

i.e.

<rebus inputQueue="myQueue" />

<rebus inputQueue="myQueue@myExchange" />

<rebus inputQueue="@myExchange" />

would determine how the endpoint sets up its binding, and the endpoint mappings

<add messages="Some.Messages" endpoint="myQueue"/>

<add messages="Some.Messages" endpoint="myQueue@myExchange"/>

<add messages="Some.Messages" endpoint="@myExchange"/>

would all be valid and would result in publishing the message to the default exchange (i.e. "Rebus") or the myExchange exchange with myQueue as the routing key, or directly to the myExchange exchange respectively.

Would that make sense? Solve all of the world's problems, maybe? ;)

pruiz commented 10 years ago

Yeah! I am currently testing this and I was just trying to make my mind about how it should work on each case (single Exchange, and One-Per-Message).

I'll get back (probably in the form of a pull-req) once I have something running.. ;)

mookid8000 commented 10 years ago

Awesome!

Sendt fra min iPhone

Den 03/06/2014 kl. 15.29 skrev Pablo Ruiz García notifications@github.com:

Yeah! I am currently testing this and I was just trying to make my mind about how it should work on each case (single Exchange, and One-Per-Message).

I'll get back (probably in the form of a pull-req) once I have something running.. ;)

— Reply to this email directly or view it on GitHub.

pruiz commented 10 years ago

Ouch, just one amendament to our previous logic:

You cannot (directly) direct a message to an specific queue across an specific exchange.. You can however, specify a routingKey against an exchange. This way this would be the available options:

<add messages="Some.Messages" endpoint="IAmAQueue" /> <add messages="Some.Messages" endpoint="@IAmAnExchange" /> <add messages="Some.Messages" endpoint="IAmARoutingKey@AtAnExchange" />

mookid8000 commented 10 years ago

Rebus never sent directly to a queue - is that even possible?

Rebus binds its input queue to a topic with the same name, that's how it's always been

pruiz commented 10 years ago

Yes, when using One-Exchage-Per type, 'send' addresses a component's queue directly (by issuing a basic.publish with exchange="" && routingKey="queueName", which is the standar way for routing to a queue on RabbitMq.

Actually my main concern is how to preserve backwards compatibility while keeping the code as simple as possible. And, by now, this is the only case which may work different between each routing strategy:

When using 'classic' routing with RabbitMQ, when sending to a mapping with endpoint="SomeThing" means we'll issue a basic.publish with exchange="Rebus" and routingKey="SomeThing".

However, when using 'one-exchange-per-type' routing, sending to a mapping with 'endpoint="SomeThing"' would mean issuing a basic.publish with exchange="" and routingKey="SomeThing".

mookid8000 commented 10 years ago

This one can be closed now, right?