Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prohibit operation specific configuration to be used for unsupported operations #275

Closed
glenthomas opened this issue Sep 26, 2017 · 16 comments

Comments

@glenthomas
Copy link

glenthomas commented Sep 26, 2017

There are many ways to configure response consumer for RPC requests and I think that many configurations do not function correctly. The header does not match the consumer setup so the response is not received.

The configurations I have tried so far and the resulting headers:

No consumer config
reply_to = amq.rabbitmq.reply-to.g2dkABNyYWJiaXRAOTk4NmQwZGExNTgzAABGtQAAAS8B.K8AGraiuM2eLasK+PQLhMQ==

consume.Consume(c => c.FromQueue("MyQueueName"))
reply_to = topic:///amq.rabbitmq.reply-to

consume.Consume(c => c.FromQueue("MyQueueName").WithRoutingKey("MyRoutingKey"))
reply_to = topic:///MyRoutingKey

consume.Consume(c => c.FromQueue("MyQueueName").OnExchange("MyExchange").WithRoutingKey("MyRoutingKey"))
reply_to = MyQueueName

consume.OnDeclaredExchange(exch => exch.WithName("MyExchange")).Consume(c => c.FromQueue("MyQueueName").WithRoutingKey("MyRoutingKey"))
reply_to = topic://MyExchange/MyRoutingKey

consume.OnDeclaredExchange(ex => ex.WithName("MyExchange")).FromDeclaredQueue(que => que.WithName("MyQueueName")).Consume(c => c.WithRoutingKey("MyRoutingKey")))
reply_to = topic://MyExchange/MyRoutingKey

consume.OnDeclaredExchange(exch => exch.WithName("MyExchange")).FromDeclaredQueue(que => que.WithName("MyQueueName"))
reply_to = topic://MyExchange/amq.rabbitmq.reply-to

Will add some more info when I have done some more testing.

@pardahlman
Copy link
Owner

Hello - hi! Thanks for the investigation. Not 100% sure what you try to achieve, though. Would you care to elaborate with more context/code samples?

In general, the requester sets ReplyTo/ReplyToAddress in its' custom BasicArgumentsMiddleware. It uses the ReplyToAddress for custom response queues. ReplyTo is only used for direct RPC.

Sounds like you try to use a custom response queue for RPC, then I would suggest use the configuration in the documentation.

In your example above.. what type is consume?

@glenthomas
Copy link
Author

glenthomas commented Sep 26, 2017

'consume' in my example is the IConsumerConfigurationBuilder used to configure the consumer part of the RequestAsync.

I was trying to figure out how to configure the request operation without reading the documentation so this is why I was having problems.. I think using the recommended configuration from the documentation will solve the problem and I will try that, but also I think the library should try to avoid making available certain configurations that do not work.

The ReplyToAddress is a pass-through property that converts between the PublicationAddress object and the ReplyTo string, so the reply_to header should be set in both cases.

public PublicationAddress ReplyToAddress
{
    get { return PublicationAddress.Parse(ReplyTo); }
    set { ReplyTo = value.ToString(); }
}

@pardahlman
Copy link
Owner

I agree with you. It is a problem that most operations add extensions to IPipeContext, which may be confusing. It could perhaps be solved by create custom derived context like IRequestPipeContext and have the request extensions only work there.

@pardahlman
Copy link
Owner

As for your comment on ReplyTo. When i developed the RPC feature I got some odd behavior when using ReplyToAddress with direct RPC. This is perhaps changed as of the latest version of the underlying client - have not verified

@pardahlman
Copy link
Owner

Nevertheless, thanks a bunch for the input - it is appreciated!

@pardahlman
Copy link
Owner

Hello again, @MrGlenThomas - I've been giving this some though but have found a good solution to only allow certain operation specific configurations tied to the operation. My best idea so far is to have a custom pipe context for operations, like

public interface IRequestPipeContext : IPipeContext { }

internal class RequestPipeContext : PipeContext, IRequestPipeContext
{
  internal RequestPipeContext(IPipeContext context)
  {
    Properties = context?.Properties;
  }
}

and add extensions method to it

public static IRequestPipeContext UseRequestConfiguration(this IRequestPipeContext context, Action<IRequestConfigurationBuilder> configuration)
{
  context.Properties.Add(PipeKey.ConfigurationAction, configuration);
  return context;
}

The problem with this is that it forces you to configure operation specific things first, as if you would do something like WithQueueSuffix() it would return the IPipeContext and the relevant extension methods would not be found.

Any ideas?

@glenthomas
Copy link
Author

I think the custom pipe context is the way to go. I have been trying to use RawRabbit v2 with my colleagues and we have spent a lot of time trying to figure out the correct configurations for each scenario we are using.

I think it may be ok to need to use the fluent API in a certain order. I'm sure I have seen this with other fluent APIs like EntityFramework and FluentValidation. I expect the end result still functions the same without any problems?

@glenthomas
Copy link
Author

I have just spent the last hour trying to figure out why my queue name was being ignored when using SubscribeAsync.

busClient.SubscribeAsync<DeleteOrderCommand>(async request => await OnRequest(request),
			    context => context.UseRespondConfiguration(responder => responder
				    .FromDeclaredQueue(queue => queue.WithName(QueueName))
					.OnDeclaredExchange(exc => exc.WithName"MyExchange")
				    .Consume(builder => builder.WithRoutingKey(RoutingKeys.DeleteOrderCommand))),
			    cancellationToken);

The code compiled and ran without any obvious issues, but the queue generated did not have the correct name.

It turned out that I had mistakenly used the UseRespondConfiguration instead of UseConsumerConfiguration. It is far too easy to make a mistake.

@pardahlman
Copy link
Owner

I hear you, @MrGlenThomas. For me, working in this code base for a long time, it makes perfect sense that Respond Configuration is intended for the Respond Operation, the Request Configuration is for the Request Operation etc. However, that rule is not valid for Subscribe which uses the basic Consumer Configuration... so I see the problem 😜.

You probably already know this, but the code compiles because all the methods on the IContext are extension methods that basically adds entries to an IDictionary<string, object>. There will always be a risk of adding an entry to the dictionary that is not picked up by the middlewares in the pipe, but it is impossible to guard against as the pipe might be manipulated by plugins, or middlewares might be reused in custom pipes etc.

I'm not 100% thrilled by the idea of operation specific pipe context. Right now I'm considering other ways to make it easier to use the right methods:

  • Putting extension operation specific extension methods in a a namespace containing the operation name. That way you get a hint that something is off if you include RawRabbit.Operations.Respond in a file where you are trying to do a SubscribeAsync
  • Check naming conventions, e.g. update the method name for configuring the Subscribe operation to UseSubscribeConfiguration and UsePublishConfiguration etc.
  • Provide a static builder classes for operation configuration (see below)
await responder.RespondAsync<BasicRequest, BasicResponse>(message =>
    Task.FromResult(sent),
  ctx => RespondConfig.Create(cfg => cfg.Consume(c => c
      .WithRoutingKey("custom_key"))
    .FromDeclaredQueue(q => q
      .WithName("custom_queue")
      .WithAutoDelete())
    .OnDeclaredExchange(e => e
      .WithName("custom_exchange")
      .WithAutoDelete()))
    .UseHostnameQueueSuffix(true)
    );

Feel free to come with more ideas around this 👍

@ChristianSauer
Copy link

ChristianSauer commented Oct 2, 2017 via email

@glenthomas
Copy link
Author

I'm not sure that static builder classes is any different to using the extension methods. Renaming the method to UseSubscribeConfiguration would be better, but it would be best if an incorrect configuration didn't compile.

pardahlman added a commit that referenced this issue Oct 4, 2017
Formerly named PublisherConfiguration
pardahlman added a commit that referenced this issue Oct 4, 2017
Formaly know as UseConsumerConfiguration
@pardahlman pardahlman changed the title RawRabbit v2 reply-to header for RPC Prohibit operation specific configuration to be used for unsupported operations Oct 4, 2017
@pardahlman
Copy link
Owner

I've started to look at this. First step is to make sure the configuration methods follow the naming convention. Might be looking at enforcing with specialized context, but before I do I need to investigate some more.

@pardahlman
Copy link
Owner

OK, some major updates related to this ticket:

  • Changed name for Subscriber's configuration extension UseSubscriberConfiguration
  • Created operation specific context that are used for specific operation
  • General extension methods made generic, so that they can be chained with a operation specific context friction free.

@glenthomas
Copy link
Author

Looks good. I am holiday on the moment and look forward to trying it out when I get home. Thanks for looking into this.

@pardahlman
Copy link
Owner

No worries - enjoy your holiday! 🌞

@pardahlman
Copy link
Owner

The fix for this has been released in rc1 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants