Forem

Rafael Andrade
Rafael Andrade

Posted on

Brighter – .NET Framework for building messaging app

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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)!;
    }
}
Enter fullscreen mode Exit fullscreen mode

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 => {});
Enter fullscreen mode Exit fullscreen mode

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>());
Enter fullscreen mode Exit fullscreen mode

or use the AutoFromAssemblies, so Brighter will scan all assemblies and register them

services.AddHostedService<ServiceActivatorHostedService>()
     .AddServiceActivator(opt => {})
     .AutoFromAssemblies();
Enter fullscreen mode Exit fullscreen mode

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"),
};
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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));
});
Enter fullscreen mode Exit fullscreen mode

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"});
Enter fullscreen mode Exit fullscreen mode

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

https://github.com/lillo42/brighter-sample/tree/basic

Top comments (0)