I have two APIs one that sends a message and another with a consumer saga to consume that message. On the send side, my message model does not implement CorrelatedBy<> interface, but has a field I would like to use as a correlationId. As I understand from the documentation, that should be configured as follows.
GlobalTopology.Send.UseCorrelationId<SubmitOrder>(x => x.OrderId);
However, after setting that, I do not see a correlationId in my message once consumed by my consumer saga, it is an empty GUID.
I have also tried another approach outlined in the documentation, but that did not produce a correlationId in my saga either.
Bus.Factory.CreateUsingRabbitMQ(..., cfg =>
{
cfg.Send<OrderSubmitted>(x =>
{
x.UseCorrelationId(context => context.Message.OrderId);
});
});
Message Interface
public interface MyEvent {
Guid MyId { get; }
DateTime Timestamp { get; }
}
Registration
builder.AddMassTransit(x => {
x.Builder.RegisterBuildCallback(bc => {
var bus = bc.Resolve<IBusControl>();
bus.Start();
});
x.AddConsumers(Assembly.GetExecutingAssembly());
x.UsingRabbitMq((context, cfg) => {
cfg.Send<MyEvent>(x => {
x.UseCorrelationId(ctx => ctx.MyId);
});
cfg.Host(new Uri(Configuration["RabbitMqHost"]), host => {
host.Username(Configuration["RabbitMqUsername"]);
host.Password(Configuration["RabbitMqPassword"]);
});
cfg.ReceiveEndpoint(Configuration["RabbitMqQueueName"], ec => {
ec.ConfigureConsumers(context);
});
cfg.ConfigureEndpoints(context);
});
});
Where am I going wrong here? I would expect the value of MyId to be assigned to CorrelationId on send and show up in the consume context of my saga on the other end.
The workaround here is simple, follow convention and name my field CorrelationId, and everything works as expected.
Consumer sagas only correlate using the CorrelatedBy<Guid> interface, which must be on the message contract. They do not use the CorrelationId property on the ConsumeContext.
Related
I am struggling to configure MassTransit with RabbitMQ to publish messages and subscribe to the queue. I think that its a simple configuration change that needs to be done. I have multiple services connected but the messages get consumed alternatively and never gets delivered / consumed on the other server.
I would like each message to get delivered to every connection / subscriber.
I am running this on ASP.net core 6 on the latest version of MassTransit.
services.TryAddSingleton(KebabCaseEndpointNameFormatter.Instance);
services.AddMassTransit(cfg =>
{
cfg.AddBus(context => Bus.Factory.CreateUsingRabbitMq(c =>
{
c.Host(connectionString, c =>
{
c.Heartbeat(10);
});
c.ConfigureEndpoints(
context,
KebabCaseEndpointNameFormatter.Instance);
c.Publish<VideoManagerResultEvent>(x =>
{
x.BindQueue("result", "video-msgs");
x.ExchangeType = Fanout;
});
c.ReceiveEndpoint("result:video-msgs", e =>
{
e.Consumer<VideoManagerResultConsumer>();
});
}));
// Request clients / DTO
RegisterRequestClients(cfg);
});
services.AddMassTransitHostedService();
}
private static void RegisterRequestClients(IServiceCollectionBusConfigurator cfg)
{
cfg.AddRequestClient<VideoManagerResultEvent>();
}
// consumer
public class VideoManagerResultConsumer : BaseConsumer<VideoManagerResultEvent>
{
public override async Task Consume(ConsumeContext<VideoManagerResultEvent> context)
{
Logger.Debug("Consumed video event");
await context.RespondAsync(new GenericResponse());
}
}
I call "SendMessage()" to publish a message to RabbitMQ.
// constructor and DI
public EventBusV2(IPublishEndpoint publishEndpoint)
{
_publishEndpoint = publishEndpoint;
}
public async Task SendMessage()
{
await _publishEndpoint.Publish<VideoManagerResultEvent>(msg);
}
To add to the original question the diagram display what is currently happening.
Diagram 1 - The request or message gets published to the eventbus but only gets delivered to the one instance of the consumer.
Diagram 2 - The required result the message gets published to both instances of the consumer.
The RaabitMQ Queue
The only configuration you should need is the following:
services.AddMassTransit(cfg =>
{
cfg.AddConsumer<VideoManagerResultConsumer>()
.Endpoint(e => e.InstanceId = "Web1"); // or 2, etc.
cfg.SetKebabCaseEndpointNameFormatter();
cfg.UsingRabbitMq((context, c) =>
{
c.Host(connectionString, c =>
{
c.Heartbeat(10);
});
c.ConfigureEndpoints(context);
});
// Request clients / DTO
RegisterRequestClients(cfg);
});
services.AddMassTransitHostedService();
Any published messages of type VideoManagerResultEvent will end up on the queue video-manager-result based upon the consumer name.
With nestJS rabbit MQ I use RPC from one server to another one. What I basically want is await this.amqpConnection.request to reject the promise when the consumer throw an error. As I understand from this and this and this documentation when you NACK (negative ack) the message it should put the RPC response to a corresponding queue with an error information which would be passed to a client. Here's some code example:
Producer
import { AmqpConnection } from '#golevelup/nestjs-rabbitmq';
let amqpConnection: AmqpConnection;
sendMessage(payload: any) {
try {
const result = await this.amqpConnection.request({
exchange: 'user',
routingKey: 'test',
queue: 'test',
payload,
});
console.log(result);
} catch (e) {
console.error(e); // I expect this flow !!!
}
}
Consumer:
#RabbitRPC({
exchange: 'user',
routingKey: 'test',
queue: 'test',
errorBehavior: MessageHandlerErrorBehavior.NACK
})
public async rpcHandler(payload: any): Promise<any> {
throw Error("Error text that should be proxied to console.error"):
}
But the message doesn't even return to the client. Client gets rejected after a timeout, rather because of an error during consumer processing. If my understanding is not correct I would like to know if there's a builtin mechanism with nestjs to reject the message w/o manually creating some response types for an error and handling it on a client.
You should throw exactly the RpcException from #nestjs/microservices and only with a certain argument
throw new RpcException({
code: 42, // some number code
message: 'wait. oh sh...', // some string value
});
Also if you using a custom exception filter then it should extends the BaseRpcExceptionFilter from #nestjs/microservices. Call the getType() method on an ArgumentsHost and if it's result equals to 'rpc' then using the super.catch method.
Is it possible to launch RabbitMQ consumer dynamically. A mean connect consumer to exist queue after specific time?
Or all consumers should be created in advance?
My case can be without consumers, when queues are filled by messages. Could I connect consumers after some time?
Yes, you could do it just like channel not yet created.
example for node.js
const conn = await amqplib.connect(`${rabbitmq.url}?heartbeat=300`);
conn.on('error', function (err) {
api.log.error('AMQP:Error:', err);
});
conn.on('close', () => {
api.log.info("AMQP:Closed");
});
const ch = await conn.createChannel();
await ch.assertQueue(queue_name, queue_options); // check if queue_name exists,
// if not, creates it
await ch.consume(queue_name, callback) // message from queue goes to callback
I have an application flow where:
Clientside Javascript triggers signalR Hub
Asynchronous Call is made
for long running operation
When operation is complete Clientside JavaScript is notified by signalR
I had assumed this would be as simple as:
Server Side:
public async void SendMessage()
{
await Task.Delay(1000);
Clients.All.SendAsync("ReceiveMessage");
}
Client Side:
var hub = new signalR.HubConnectionBuilder().withUrl('/disposeBugHub').build();
this.hub.on("ReceiveMessage", function () {
alert('Message Received');
});
this.hub.start()
.then(function () {
hub.invoke("SendMessage");
})
.catch(function (err) {
return console.error(err.toString());
});
However the Clients.All.SendAsync("ReceiveMessage"); call always throws a System.ObjectDisposedException: 'Cannot access a disposed object.' Exception.
This appears to be expected behavoir and not a bug, so my question is how do i programmatically acheive the desired workflow? I assume there must be a well known pattern to acheive this but I cant find it online.
First of all , remove void method for long running process. use Task return type method .
Try this.
public async Task SendMessage()
{
await Task.Delay(10000);
Clients.All.SendAsync("ReceiveMessage");
}
I have a model that will subscribe a websocket to several expensive end points when the user is on particular routes. When the user leaves the route, I want to disconnect the websockets.
The dva api docs say
Notice: if we want to unregister a model with app.unmodel(), it's subscriptions must return unsubscribe method.
However the docs do not include how to register a subscription with an unsubscribe method.
How does one create a subscription with an unsubscribe handler?
It's necessary to add return to the end of your function.
subscriptions: {
setup() {
emitter.on('event', () => {
emitterCount += 1;
});
return () => {
emitter.removeAllListeners();
};
},
},