If you need to .NET build an app that requires a queue/stream, Paramore. Brighter is an excellent option, it's based on enterprise integration patterns. It's very versatile, and it has a lot of useful packages like distributed lock and outbox patterns.
Project
In this project I’ll show how to send and consume a message using Brighter with RabbitMQ, you will need a podman (to run RabbitMQ) and .NET 8 or 9
Packages
For this project, it’s necessary to install these packages
Paramore.Brighter.ServiceActivator - Allows you to consume messages
Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection - Useful method to register Brighter
Paramore.Brighter.ServiceActivator.Extensions.Hosting - Add support startup message consumer in the background
Paramore.Brighter.MessagingGateway.RMQ - RabbitMQ package
Messages
All messages in Brighter need to implement the IRequest
interface.
Brighter has a concept of Event
& Command
on the messages, where a command you have only one consumer and it allows to reply to the request and events are something that has already happened and can be handled by multiple consumers.
public class Greeting(Guid id) : Event(id)
{
public string Name { get; set; } = string.Empty;
}
Handlers
Handlers are the consumer part of the app, where we can consume a message in an async or sync way.
public class GreetingHandler : RequestHandler<Greeting>
{
private readonly ILogger<GreetingHandler> _logger;
public GreetingHandler(ILogger<GreetingHandler> logger)
{
_logger = logger;
}
public override Greeting Handle(Greeting command)
{
_logger.LogInformation("Hello {Name}", command.Name);
return base.Handle(command);
}
}
Message mapper
On Brighter, we have a concept of Message (Header and Body) this decouple allows you to have multiple ways to serialize the messages, like for message A serialize using Json, for message B gRPC etc. Another advantage is it allows us to use one type internally before sending a map to another structure. For this project let's use a JSON serializer.
public class GreetingMapper : IAmAMessageMapper<Greeting>
{
public Message MapToMessage(Greeting request)
{
var header = new MessageHeader();
header.Id = request.Id;
header.TimeStamp = DateTime.UtcNow;
header.Topic = "greeting.event";
header.MessageType = MessageType.MT_EVENT;
var body = new MessageBody(
JsonSerializer.Serialize(request, JsonSerialisationOptions.Options)
);
return new Message(header, body);
}
public Greeting MapToRequest(Message message)
{
return JsonSerializer.Deserialize<Greeting>(message.Body.Bytes)!;
}
}
Register Brighter
For the next step, we need a HostBuilder
where we are going to register Brighter
using the AddServiceActivator
, passing our subscription (message consumers).
services.AddHostedService<ServiceActivatorHostedService>()
.AddServiceActivator(opt => {});
Then we have 2 options, register everything manually
services.AddHostedService<ServiceActivatorHostedService>()
.AddServiceActivator(opt => {})
.Handlers(register => register.Register<Greeting, GreetingHandler>())
.MapperRegistry(register => register.Register<Greeting, GreetingMapper>());
or use the AutoFromAssemblies
, so Brighter
will scan all assemblies and register them
services.AddHostedService<ServiceActivatorHostedService>()
.AddServiceActivator(opt => {})
.AutoFromAssemblies();
We need to setup the RabbitMQ connection and pass it to ChannelFactory & ProducerRegistryFactory.
var rmqConnection = new RmqMessagingGatewayConnection
{
AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
Exchange = new Exchange("paramore.brighter.exchange"),
};
With this connection, we need to set a publication, for this project let's keep it simple by creating the queue if it does not exist (MakeChannels = OnMissingChannel.Create
) and setting the queue name (with Topic
property)
var rmqConnection = new RmqMessagingGatewayConnection
{
AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
Exchange = new Exchange("paramore.brighter.exchange"),
};
services.AddServiceActivator(opt => {})
.UseExternalBus(new RmqProducerRegistryFactory(rmqConnection,
new RmqPublication[]
{
new()
{
MakeChannels = OnMissingChannel.Create,
Topic = new RoutingKey("greeting.event"),
},
}
).Create());
The last part is setting up a consumer (queue subscription) and we configure it on AddServiceActivator
by setting Subscriptions
& Channel
properties
services.AddServiceActivator(opt =>
{
opt.Subscriptions = new Subscription[]
{
new RmqSubscription<Greeting>(
new SubscriptionName("paramore.example.greeting"),
new ChannelName("greeting.event"),
new RoutingKey("greeting.event"),
makeChannels: OnMissingChannel.Create),
};
opt.ChannelFactory = new ChannelFactory(new RmqMessageConsumerFactory(rmqConnection));
});
Publishing a message
Now we have everything set we can publish a message by
var processor = host.Services.GetRequiredService<IAmACommandProcessor>();
processor.Post(new Greeting(Guid.NewGuid()) { Name = "hello"});
Conclusion
Brighter is a nice framework, but it requires knowledge of enterprise integration patterns to understand the configuration of some Brighter design choices. I'm going to do more articles talking about enterprise integration patterns and Brighter.
Reference
https://brightercommand.gitbook.io/paramore-brighter-documentation
Top comments (0)