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();
Related
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:
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>());
I am currently working with micro service architecture and .net core.
Rabbit MQ + MassTransit are being used to send and receive data between the micro services.
I have a host application in IIS and from 2 separate browsers I send the same request to micro service and that microservice calls other service using RabbitMQ.
I expect to get 2 separate requests hitting the consumer but instead get an internal server error.
Startup:
services.AddScoped<OrderCompletedEventConsumer>();
services.AddMassTransit(x =>
{
x.AddConsumer<Controllers.OrderController>();
});
services.AddSingleton(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("http://192.168.101.111:5672"),
"/", h =>
{
h.Username("Test");
h.Password("test");
});
cfg.ReceiveEndpoint(host, "TestQUE", e =>
{
e.Consumer<Controllers.OrderController>(provider);
});
}));
//Register Publish Endpoint of RabbitMQ bus service
services.AddSingleton<IPublishEndpoint>(provider => provider.GetRequiredService<IBusControl>());
//Register Send Endpoint of RabbitMQ bus service
services.AddSingleton<ISendEndpointProvider>(provider => provider.GetRequiredService<IBusControl>());
//Register Bus control for RabbitMQ
services.AddSingleton<IBus>(provider => provider.GetRequiredService<IBusControl>());
//Regster Bus Service hosting
services.AddSingleton<IHostedService, BusService>();
Request From One microservice:-
IRequestClient<IAddRequest<IOrder>, IAddResponse<IOrder>> orderClient =
new MessageRequestClient<IAddRequest<IOrder>, IAddResponse<IOrder>>(_bus,
EndpointAddress("orderQue"), TimeSpan.FromSeconds(Convert.ToDouble("150")));
var addResponse = orderClient.Request(new
{
entity = order
});
await Task.WhenAll(addResponse);
Consumer
public async Task Consume(ConsumeContext<IGetRequest<IOrder>> context)
{
// Operation and return result
await context.RespondAsync<IGetResposne<IOffice>>(new
{
// send result.
});
}
In the consumer the 2 separate requests from different browser arrive but both are unsuccessful. However if I do one request at a time then it will work, why is this?
Please give any idea, suggestion or hint.
Thank you,
I want to webapplication and in the backend I open a new thread and start a listener.What I want ,open connection one time and rabbitmq listener start and when new message is coming ,it is processed in background.
What is the best approach for this?
Why do you need to listen events in web application?
Instead of it, write a windows service using topshelf and masstransit as a rabbitmq client.
You can connect to rabbitmq and register listener consumers in Program.cs as below:
IBusControl busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
IRabbitMqHost host = cfg.Host(new Uri(RabbitMQConstants.RabbitMQUri),
hst =>
{
hst.Username(RabbitMQConstants.RabbitMQUserName);
hst.Password(RabbitMQConstants.RabbitMQPassword);
});
cfg.ReceiveEndpoint(host,
RabbitMQConstants.YourQueueName,
endPointConfigurator => { endPointConfigurator.Consumer<SomeConsumer>(); });
});
And start bus configurator :
busControl.Start();
Example consumer:
public class SomeConsumer :
IConsumer<YourMessageClass>
{
public async Task Consume(ConsumeContext<YourMessageClass> context)
{
await Console.Out.WriteLineAsync($"Message consumed: {context.Message.YourValue}");
}
}
For more information and example take a look at offical masstransit documentation : http://masstransit-project.com/MassTransit/usage/message-consumers.html
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"));
});