EasyNetQ - Consuming message from IAdvanceBus by non generic message - c#

Yes we can use auto subscriber functionality and normal subscribe method with this subscription id but this solution is bit ugly in RMQ queues and exchanges. It was difficult to follow and analyze the messages.
I use the advance bus and created my own exchanges and queues. I published successfully but the consuming part is bit disappointment. Currently, it uses this way:
IAdvanceBus = bus.Advanced.Consume(queueName, registration =>
{
registration.Add<MESSAGE1>((message, info) => { ProcessMessage(MESSAGE1) })
registration.Add<MESSAGE2>((message, info) => { ProcessMessage(MESSAGE2) })
registration.Add<MESSAGE3>((message, info) => { ProcessMessage(MESSAGE3) })
registration.Add<MESSAGE4>((message, info) => { ProcessMessage(MESSAGE4) })
registration.Add<MESSAGE5>((message, info) => { ProcessMessage(MESSAGE5) });
});
This is good but the problem if you have hundred listeners?
I checked the registration type IHandlerRegistration it uses only generic one, can we have the none generic way?
Like :
IAdvanceBus = bus.Advanced.Consume(queueName, registration =>
{
registration.Add(typeof(MESSAGE1), info => { ProcessMessage(MESSAGE1) })
registration.Add(typeof(MESSAGE2), info => { ProcessMessage(MESSAGE2) })
registration.Add(typeof(MESSAGE3), info => { ProcessMessage(MESSAGE3) })
});
In this way we can scan the assembly who uses this messages.
In other side, I register via construction of bus:
RabbitHutch.CreateBus(connectionString, registeredServices => {
IEasyNetQLogger logger;
MyCustomHandlerCollection myHandlers = myDIContainer.Resolve<IMyHandlers>();
registeredServices.Register<IHandlerCollection>(s =>
{
logger = s.Resolve<IEasyNetQLogger>();
return myHandlers;
});
registeredServices.Register<IHandlerRegistration>(s => myHandlers});});
But it not respecting my regisration because when I see the code from advance bus consuming:consume code it creates from factory and NOT reading from container. I believe this is a root cause.

To workaround this requirement, I used this method from IAdvanceBus :
IDisposable Consume(IQueue queue, Func<byte[], MessageProperties, MessageReceivedInfo, Task> onMessage);
I rolled my own message dispatcher and deserialize any message from the queue. The collection dispatcher will determine the message type and dispatch to specific handler created using reflection.

Related

MassTransit custom Query/Command topology for Request/Response mechanism

I'm currently reworking a microservices-based solution into a modular monolith with four APIs (pro, cyclist, management, thirdparty). One of the changes that need to be done is adapting the topology of our broker (RabbitMQ) so it fits our requirements. These requirements are shown on the diagram below.
The idea is that we currently always use the Request/Response mechanism for all our commands and queries and Publish mechanism for events, meaning that we always expect a response, whenever issuing a query (obviously) or a command.
We want the topology to support scaling in a way that if API1 (any instance of this executable) has multiple instances
commands/queries issued by any instance of the API1 will be executed by the consumers running in any instance of the API1 - this means that if both API1 and API2 executables have the same consumer, API2 consumers cannot execute commands/queries issued by the API2
when scaling, queues for commands and queries should not be scaled, just new consumers will be added and round robin should fire up
events are always received by all registered consumers so when scaling new queues are created
Right now I'm trying to figure out how to create this topology in MassTransit but I can't seem to get rid of the default publish exchange of type fanout. Here's the code that I use for automatic registration of command/queries endpoints and queues
private static IRabbitMqBusFactoryConfigurator AddNonEventConsumer<TConsumer>(
IRabbitMqBusFactoryConfigurator config,
IRegistration context)
where TConsumer : class, IConsumer
{
var routingKey = Assembly.GetEntryAssembly().GetName().Name;
var messageType = typeof(TConsumer)
.GetInterfaces()
?.First(i => i.IsGenericType)
?.GetGenericArguments()
?.First();
if (messageType == null)
{
throw new InvalidOperationException(
$"Message type could not be extracted from the consumer type. ConsumerTypeName=[{typeof(TConsumer).Name}]");
}
config.ReceiveEndpoint(e =>
{
// var exchangeName = new StringBuilder(messageType.FullName)
// .Replace($".{messageType.Name}", string.Empty)
// .Append($":{messageType.Name}")
// .ToString();
var exchangeName = messageType.FullName;
e.ConfigureConsumeTopology = false;
e.ExchangeType = ExchangeType.Direct;
e.Consumer<TConsumer>(context);
e.Bind(exchangeName, b =>
{
e.ExchangeType = ExchangeType.Direct;
b.RoutingKey = routingKey;
});
});
config.Send<TestCommand>(c =>
{
c.UseRoutingKeyFormatter(x => routingKey);
});
config.Publish<TestCommand>(c =>
{
c.ExchangeType = ExchangeType.Direct;
});
return config;
}
Again, we do want to use Request/Response mechanism for queries/commands and Publish mechanism for events (events are not a part of this question, it's a topic on its own, just queries/commands).
The question is - how do I configure endpoints and queues in this method in order to achieve the desired topology?
Alternative question - how else can I achieve my goal?
Cyclist? Pro? What kind of modular monolith is this anyway??
You're almost there, but need to configure a couple of additional items. First, when publishing, you'll need to set the routing key, which can be done using a routing key formatter. Also, configure the message type to use a direct exchange.
configurator.Send<TestCommand>(x =>
{
x.UseRoutingKeyFormatter(context => /* something that gets your string, pro/cyclist */);
});
config.Publish<TestCommand>(c =>
{
c.ExchangeType = ExchangeType.Direct;
});
Also, if you're using custom exchange names, I'd add a custom entity name formatter. This will change the exchange names used for message types, so you can stick with message types in your application – keeping all the magic string stuff in one place.
class CustomEntityNameFormatter :
IEntityNameFormatter
{
public string FormatEntityName<T>()
where T : class
{
return new StringBuilder(typeof(T).FullName)
.Replace($".{typeof(T).Name}", string.Empty)
.Append($":{typeof(T).Name}")
.ToString();
}
}
config.MessageTopology
.SetEntityNameFormatter(new CustomEntityNameFormatter());
Then, when configuring your receive endpoint, do not change the endpoint's exchange type, only the bound exchange to match the publish topology. Using an endpoint name formatter, custom for you application, you can configure it manually as shown.
var routingKey = Assembly.GetEntryAssembly().GetName().Name;
var endpointNameFormatter = new CustomEndpointNameFormatter();
config.ReceiveEndpoint(endpointNameFormatter.Message<TMessage>(), e =>
{
e.ConfigureConsumeTopology = false;
e.Bind<TMessage>(b =>
{
e.ExchangeType = ExchangeType.Direct;
b.RoutingKey = routingKey;
});
e.Consumer<TConsumer>(context);
});
This is just a rough sample to get your started. There is a direct exchange sample on GitHub that you can look at as well to see how various things are done in there. You could likely clean up the message type detection as well to avoid having to do all the type based reflection stuff, but that's more complex.

Mass Transit Filter for all Consumers/All Message Types

I'm trying to create a filter that would be executed for all message types. Ideally, you would only have to register the filter once, instead of doing it for each consumer. (I want to do the same thing on the publish side as well). I would need it to be within a lifetime scope. It's just going to pop a value out of a header, and assign it to Lifetime-scoped object that my DI container will provide (publish side does the reverse)
I watched the Chris Patterson's twitch video on Middleware, and I think it comes close to want I want around the 38 minute mark, but he registers the filter for a specific consumer. On the consumer side, I think I need a filter off of the ConsumeContext, but I just don't know how to register the filter in a way that it will be used for all consumers. I'm using MT 7 and Autofac. Can anyone show me some example code on how to register a scoped filter that will work for all consumers? (and if it's very different, one that will work for all publishers)?
If you need a filter that is in the lifetime scope, you need to use scoped filters (requires MassTransit v7). This will register the filter for any consumer, so that it is executed. You do need to make your filter generic, with T as a message type, which you can choose to use or ignore.
public class MyFilter<T> :
IFilter<ConsumeContext<T>>
where T : class
{
SomeScopedObject _obj;
public MyFilter(SomeScopedObject obj)
{
_obj = obj;
}
public async Task Send(ConsumeContext<T> context, IPipe<ConsumeContext<T>> next)
{
// do your thing with _obj
await next.Send(context);
}
public void Probe(ProbeContext context)
{
}
}
Then, on your receive endpoint(s), configure the filter before the consumers.
e.UseConsumeFilter(typeof(MyFilter<>));
This will configure for every consumer/message a version of your filter that executes within the container scope of the consumer.
You can do the same for publish/send.
Documentation is on the site.
Have you checked the docs? It has the configuration example with retries being configured on the endpoint level and the consumer level.
Bus.Factory.CreateUsingInMemory(cfg =>
{
cfg.ReceiveEndpoint("input-queue", e =>
{
e.UseMessageRetry(r =>
{
r.Immediate(5);
r.Handle<DataException>(x => x.Message.Contains("SQL"));
});
e.Consumer<MyConsumer>(c => c.UseMessageRetry(r =>
{
r.Interval(10, TimeSpan.FromMilliseconds(200));
r.Ignore<ArgumentNullException>();
r.Ignore<DataException>(x => x.Message.Contains("SQL"));
});
);
});
});

How to discard faulted messages when using the ConfigureEndpoints method in MassTransit?

This article provides an example of how to do this when configuring an endpoint manually.
Just like this:
cfg.ReceiveEndpoint("input-queue", ec =>
{
ec.DiscardFaultedMessages();
});
But I've got a lot of consumers and I don't want to configure each of them manually, so I use methods AddConsumers and ConfigureEndpoints.
Like this:
services.AddMassTransit(cfg =>
{
cfg.AddConsumers(Assembly.GetExecutingAssembly());
cfg.AddBus(sp => Bus.Factory.CreateUsingRabbitMq(x => x.ConfigureEndpoints(sp)));
});
If I additionally call the ReceiveEndPoint method (before or after calling ConfigureEndpoints), the exception "A receive endpoint with the same key was already added" is thrown.
Is there a way to configure a specific endpoint when using ConfigureEndpoints method?
When using ConfigureEndpoints, consumers, sagas, and activites are configured on receive endpoints automatically. To configure the receive endpoint for a specific consumer, create a consumer definition. If scanning an assembly for consumers, matching consumer definitions will also be discovered.
public class SubmitOrderConsumerDefinition :
ConsumerDefinition<SubmitOrderConsumer>
{
protected override void ConfigureConsumer(IReceiveEndpointConfigurator endpointConfigurator,
IConsumerConfigurator<SubmitOrderConsumer> consumerConfigurator)
{
endpointConfigurator.DiscardFaultedMessage();
}
}

UseSendExecute in ConfigurePublish in MassTransit 5.2.1

In MassTransit 5.2.0 I used the method UseSendExecute inside ConfigurePublish for adding headers to a particular message type:
var bus = BusConfigurator.ConfigureBus((h,cfg) =>
{
h.ConfigurePublish(x =>
x.UseSendExecute<TestMessage>(p =>
{
p.Headers.Set("x-deduplication-header", p.Message.Day.Ticks);
}));
});
After upgrading to MassTransit 5.2.1 the method UseSendExecute is not longer available inside ConfigurePublish.
Digging a little inside MassTransit changes I found that the problem happens because MassTransit.IPublishPipeConfigurator no longer implements MassTransit.ISendPipeConfigurator and UseSendExecute is an extension method for ISendPipeConfigurator inside MassTransit.DelegatePipeConfiguratorExtensions.
How can I set the same publish configuration using 5.2.1 version?
Well, you're configuring a publish behavior. The new syntax would be:
var bus = BusConfigurator.ConfigureBus((h,cfg) =>
{
h.ConfigurePublish(pc => pc.AddPipeSpecification(
new DelegatePipeSpecification<PublishContext<TestMessage>>(p =>
{
p.Headers.Set("x-deduplication-header", p.Message.Day.Ticks);
})));
});

ReactiveUI: Why do I have to specify the scheduler explicitly in "...Throttle..."when using the TestScheduler

I'm new to ReactiveUI. I have the following simple setup: a path to a csv can be specified and the containing datapoints will be displayed to the user (using oxyplot).
Now I'm trying to test the following subscription:
public GraphViewModel(IScreen hostScreen)
{
HostScreen = hostScreen;
setupGraphFormatting();
// Data Loading if path is valid
this.WhenAnyValue(viewModel => viewModel.PathToDataCsv)
.ObserveOn(RxApp.MainThreadScheduler)
.Throttle(TimeSpan.FromMilliseconds(500), RxApp.TaskpoolScheduler)
.Select(csvPath => csvPath?.Trim('"'))
.Where(csvPath => !string.IsNullOrEmpty(csvPath) && File.Exists(csvPath))
.Subscribe(csvPath =>
{
csvPath = csvPath?.Trim('"');
updatePlotModel(csvPath);
}, exception => {});
/* additional Code*/
}
And that's the corresponding UnitTest:
[Test]
public void If_PathToDataCsv_has_a_valid_value()
{
new TestScheduler().With(scheduler =>
{
string pathToValidCsvFile = "data.log";
var viewModel = new GraphViewModel(null);
scheduler.AdvanceByMs(1000);
viewModel.PathToDataCsv = pathToValidCsvFile;
scheduler.AdvanceByMs(1000);
viewModel.PlotModel.Series.Count.Should().Be(6);
});
}
My first implementation of WhenAnyValue didn't set any of the Schedulers specifically ( in Throttle and lacking any ObserverOn ):
public GraphViewModel(IScreen hostScreen)
{
HostScreen = hostScreen;
setupGraphFormatting();
// Data Loading if path is valid
this.WhenAnyValue(viewModel => viewModel.PathToDataCsv)
.Throttle(TimeSpan.FromMilliseconds(500))
.Select(csvPath => csvPath?.Trim('"'))
.Where(csvPath => !string.IsNullOrEmpty(csvPath) && File.Exists(csvPath))
.Subscribe(csvPath =>
{
csvPath = csvPath?.Trim('"');
updatePlotModel(csvPath);
}, exception => {});
/* additional Code*/
}
But then my Unittest failed. My assumption was that TestScheduler was being used for Throttle behind the scenes and I didn't have to do anything. Am I doing something wrong or is this the right way: If I want to use TestScheduler/TimeTravelâ„¢ I have to specify the schedulers the way I did?
Edit in response to Glenn Watsons answer:
Ok, now it's clear: The methods in question (Throttle, ObserverOn) of course do not use ReactiveUI's Schedulers, because these are methods from the Reactive Extensions Framework. So they can't be replaced implicitly by ReactiveUI in case of a UnitTest except I tell the methods to use the RxApp Schedulers...
RxApp provides the ThreadPoolScheduler when you are in release mode, and the testing scheduler when you are in unit test mode.
By default the reactive extensions (separate to ReactiveUI) will use their own default schedulers which are unaware of unit tests.

Categories