CreateQueues and Exchanges - MassTransit - rabbitmq

I'm using massTransit with RabbitMQ. Publishing messages with massTransit will create an exchange for my message type. And a masstransit consumer will create queues and bindings to an exchange. Great, make things easy.
Before looking at massTransit I used rabbitMQ's api to create queues, exchanges and binding. I would get both publisher and consumers to run the same setup code. So no matter who ran first all queues, exchanges and binding will be created no matter which part of the application ran first. This was great when running in a development environment.
I was wondering if something similar could be achieved with massTransit?

With MassTransit should be the same: consumers will create queues bound to the exchanges of the messages they consume (with names equal to the messages types).
Publishers will create the exchanges with same names of the types of the messages they publish.
Remember that if the messages published or consumed have super classes or implement interfaces, MassTransit will create the same hierarchy, creating and binding as many exchanges as your message class hierarchy has.

You could use HareDu 2 to achieve this with the below code. This works with both Autofac and .NET Core DI. Check the docs here: https://github.com/ahives/HareDu2
// Create a queue
var result = _container.Resolve<IBrokerObjectFactory>()
.Object<Queue>()
.Create(x =>
{
x.Queue("fake_queue");
x.Configure(c =>
{
c.IsDurable();
c.AutoDeleteWhenNotInUse();
c.HasArguments(arg =>
{
arg.SetQueueExpiration(1000);
arg.SetPerQueuedMessageExpiration(2000);
});
});
x.Targeting(t =>
{
t.VirtualHost("fake_vhost");
t.Node("fake_node");
});
});
// Create an exchange
var result = _container.Resolve<IBrokerObjectFactory>()
.Object<Exchange>()
.Create(x =>
{
x.Exchange("fake_exchange");
x.Configure(c =>
{
c.IsDurable();
c.IsForInternalUse();
c.HasRoutingType(ExchangeRoutingType.Fanout);
c.HasArguments(arg =>
{
arg.Set("fake_arg", "fake_arg_value");
});
});
x.Targeting(t => t.VirtualHost("fake_vhost"));
});
// Create a binding
var result = _container.Resolve<IBrokerObjectFactory>()
.Object<Binding>()
.Create(x =>
{
x.Binding(b =>
{
b.Source("fake_exchange");
b.Destination("fake_queue");
b.Type(BindingType.Exchange);
});
x.Configure(c =>
{
c.HasRoutingKey("your_routing_key");
c.HasArguments(arg =>
{
arg.Set("your_arg", "your_arg_value");
});
});
x.Targeting(t => t.VirtualHost("fake_vhost"));
});

Related

How to set unique queue name for ActiveMQ in MassTransit?

In StartUp of the project, I make the following settings for MassTransit.ActiveMQ. But when I run, it creates two queues for me, one is event-listener and the other is called Generation.
When I publish information, the information goes into the queues generated by the system.
But I want the information to be published inside queue event-listener that I set.
Please guide me
services.AddMassTransit(x =>
{
x.AddConsumer<EventConsumer>();
x.UsingActiveMq((context, cfg) =>
{
cfg.Host("localhost", h =>
{
h.Username("admin");
h.Password("admin");
});
cfg.ReceiveEndpoint("event-listener", e =>
{
e.ConfigureConsumer<EventConsumer>(context);
});
});
});
MassTransit will only create queues for configured consumers, or explicitly configured receive endpoints. In the code above, the only queue created would be called event-listener. For each message type consumed by the consumer, a topic is created and a virtual topic consumer is created so that the receive endpoint can consume messages of each type.
When messages are published, a topic is created for each published message type.
If you want to send a message directly to a queue, instead of publishing:
var provider = serviceProvider.GetRequiredService<ISendEndpointProvider>();
var endpoint = await provider.GetSendEndpoint(new Uri("queue:event-listener"));
await endpoint.Send(...);

How to use a specified topic in a saga when using MassTransit on Azure ServiceBus

I would like all the messaging for a particular saga to all take place on the same topic.
I set my saga up like so:
public void ConfigureServices(IServiceCollection services)
{
services.AddMassTransit(x =>
{
var machine = new MyStateMachine();
var repository = new InMemorySagaRepository<MyState>();
x.UsingAzureServiceBus((ctx,cfg) => {
cfg.Host(config.ServiceBusConnectionString);
cfg.SubscriptionEndpoint("mySub", "myTopic", e =>
{
e.StateMachineSaga(machine, repository);
});
});
});
}
In the state machine I do this:
Initially(
When(FirstEvent)
.PublishAsync(context => context.Init<SecondMessage>(new { TestParam = "test"}))
.TransitionTo(FirstState));
I would like the SecondMessage to be published on the topic 'myTopic' but instead MassTransit creates a new topic for this message.
MassTransit creates a topic per message type, and publishes messages to their corresponding topics. Assigning the same topic name to multiple message types is not recommended.
If you want to use a subscription endpoint for a saga, you would need to configure the saga on the corresponding topic for each event. By default, MassTransit will forward the message topics for each saga event to the receive endpoint queue.
An example topology is shown below:

Can we use RabbitMQ and Mediatr together using masstransit?

I created a microservice application that microservices using MassTransit and RabbitMQ for communication.
Each microservice developed using clean architecture, so we have MediatR inside each microservice.
Is it possible to use MassTransit for inside communication as well? so I can use the same signature for all services and when I want to expose a service to be used inter-microservice, it will be doable with ease.
So MediatR used for intra-communication and RabbitMQ used for inter-communication, and whole universe is on MassTransit system.
[Update] My question is how we can configure consumers so some can be used for inside communication (via MediatR) and some can be used for external communication (via RabbitMQ) and easily change them from inside to outside.
[Update2] for example here is my MassTransit registration:
services.AddMassTransit(x =>
{
x.AddConsumers(Assembly.GetExecutingAssembly());
x.AddBus(provider =>
Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(new Uri(config.RabbitMQ.Address), h =>
{
h.Username(config.RabbitMQ.Username);
h.Password(config.RabbitMQ.Password);
});
cfg.ReceiveEndpoint("my-queue", ep => { ep.ConfigureConsumers(provider); });
}));
x.AddMediator((provider, cfg) => { cfg.ConfigureConsumers(provider); });
});
How can I differ in internal communication and external communication? in other words, how can I register some consumers to MediatR and some to RabbitMQ?
They can be used together, and MassTransit has its own Mediator implementation as well so you can write your handlers once and use them either via the mediator or via a durable transport such as RabbitMQ.
There are videos available that take you through the capabilities, starting with mediator and moving to RabbitMQ.
I found that I should create a separate bus for each. then external services inherit from an interface like IExternalConsumer, so I can separate them form internal ones and add them to related bus:
UPDATED for version 7
// find consumers
var types = AssemblyTypeCache.FindTypes(new[]{Assembly.GetExecutingAssembly()},TypeMetadataCache.IsConsumerOrDefinition).GetAwaiter().GetResult();
var consumers = types.FindTypes(TypeClassification.Concrete | TypeClassification.Closed).ToArray();
var internals = new List<Type>();
var externals = new List<Type>();
foreach (Type type in consumers)
{
if (type.HasInterface<IExternalConsumer>())
externals.Add(type);
else
internals.Add(type);
}
services.AddMediator(x =>
{
x.AddConsumers(internals.ToArray());
x.ConfigureMediator((provider, cfg) => cfg.UseFluentValidation());
});
services.AddMassTransit<IExternalBus>(x =>
{
x.AddConsumers(externals.ToArray());
x.AddBus(provider =>
Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(new Uri(config.RabbitMQ.Address), h =>
{
h.Username(config.RabbitMQ.Username);
h.Password(config.RabbitMQ.Password);
});
cfg.ReceiveEndpoint(apiProviderName, ep => { ep.ConfigureConsumers(provider); });
}));
});
services.AddMassTransitHostedService();

Call AddConsumer using generic type

I have a problem when I am using MassTransit v5.1.5 with the default ASP.NET Core DI. I have the following code:
var consumers = typeof(CompanyApplicationService).Assembly
.GetTypes()
.Where(t => typeof(IConsumer).IsAssignableFrom(t))
.ToList();
consumers.ForEach(
c => services.AddSingleton(typeof(IConsumer), c));
cqrsConfig.ServiceCollectionConfig = (x) =>
{
consumers.ForEach(consumer => x.AddConsumer<consumer>());
};
So I have a separate assembly where my implementations of IConsumer<T> are. I have those consumers loaded into a list, but when I try to add them using the MassTransit.ExtensionsDependencyInjectionIntegration.IServiceCollectionConfigurator.AddConsumer<T>() method I cannot pass the type that I have previously loaded. So any ideas on this?
I have tried to register the consumers like:
cqrsConfig.InMemoryBusConfig = (c) =>
{
var host = c.Host;
c.ReceiveEndpoint(busName, ep =>
{
ep.LoadFrom(services.BuildServiceProvider());
});
};
but that also doesn't work for me.
There's more than one thing that is wrong here.
In order to know, which message to handle, MassTransit needs to know the generic interface type of the consumer. You, however, register all consumers as IConsumer, which is apparently wrong.
Consumers are by definition isolated to the message scope. So, consumers are instantiated and disposed for each message, and, therefore, cannot be singletons.
You need to register your consumers like it is described in the documentation:
services.AddScoped<OrderConsumer>();
or
services.AddMassTransit(x =>
{
x.AddConsumer<OrderConsumer>();
});
When consumers are added to the service collection, you also need to register the bus as it is shown in the documentation, so you use the service provider delegate:
services.AddSingleton(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host("localhost", "/", h => { });
cfg.ReceiveEndpoint(host, "submit-order", e =>
{
e.LoadFrom(provider);
});
}));
services.AddSingleton<IBus>(provider => provider.GetRequiredService<IBusControl>());

Keep messages in queue while consumer is offline

I use Masstransit in C# project.
I have a publisher and consumer services, and when both of them are up, then there are no problems. But if the consumer goes offline, published messages don't go to the queue. They just disappear.
The expected behavior is to keep messages in the queue until the consumer is started, and then send them to it. I've found several topics in google groups with same questions, but it wasn't clear for me how to solve that problem.
It seems strange to me that this functionality isn't provided out of the box because, in my understanding, it is the main purpose of RabbitMQ and MT.
The way I create publisher bus:
public static IBusControl CreateBus()
{
return Bus.Factory.CreateUsingRabbitMq(sbc =>
{
var host = sbc.Host(new Uri("rabbitmq://RMQ-TEST"), h =>
{
h.Username("test");
h.Password("test");
});
sbc.ReceiveEndpoint(host, "test_queue", ep =>
{
ep.Handler<IProductDescriptionChangedEvent>(
content => content.CompleteTask);
});
});
}
And the consumer:
public static void StartRmqBus()
{
var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://RMQ-TEST"), h =>
{
h.Username("test");
h.Password("test");
});
cfg.ReceiveEndpoint(host, "test_queue", ep =>
{
ep.Consumer<ProductChangedConsumer>();
});
});
bus.Start();
}
EDIT:
Here is one more interesting feature: if I stop both services and manually put a message to the queue via admin interface of MT, the message is waiting in test_queue. But when I start publisher or consumer service, it falls to test_queue_error queue.
You use the same queue for published and consumer, plus publisher has a consumer for this message type, as you pointed out in your own answer.
If your publisher does not consume messages, it is better to remove the receiving endpoint from it at all and then your service will be send-only.
If you have several services, where each of them need to have their own consumers for the same message type - this is how pub-sub works and you must have different queues per service. This is described in the Common Gotchas section of the documentation. In such scenario, each service will get it's own copy of the published message.
If you have one queue - you get competing consumers and this scenario is only valid for horizontal scalability, where you run several instance of the same services to increase the number of processed messages if the processing is too slow. In such case all these instances will consume messages from the same queue. In this scenario only one instance will get a message.
It seems like my publisher was set up incorrectly. After removing this part:
sbc.ReceiveEndpoint(host, "test_queue", ep =>
{
ep.Handler<IProductDescriptionChangedEvent>(
content => content.CompleteTask);
});
it started to work as expected. Looks like it consumed its own messages, that's why I didn't see messages in the queue when the consumer was down.