LogoLogo
  • README
  • VERSION 9
    • Version Begins
  • Overview
    • Show me the code!
    • Basic Concepts
    • Why Brighter?
  • Brighter Configuration
    • Basic Configuration
    • How Configuring the Command Processor Works
    • How Configuring a Dispatcher for an External Bus Works
    • RabbitMQ Configuration
    • AWS SNS Configuration
    • Kafka Configuration
    • Azure Service Bus Configuration
    • Azure Archive Provider Configuration
  • Darker Configuration
    • Basic Configuration
  • Brighter Request Handlers and Middleware Pipelines
    • Building an Async Pipeline of Request Handlers
    • Basic Configuration
    • How to Implement an Async Request Handler
    • Requests, Commands and an Events
    • Dispatching Requests
    • Dispatching An Async Request
    • Returning results from a Handler
    • Using an External Bus
    • Message Mappers
    • Routing
    • Building a Pipeline of Request Handlers
    • Passing information between Handlers in the Pipeline
    • Failure and Dead Letter Queues
    • Supporting Retry and Circuit Breaker
    • Failure and Fallback
    • Feature Switches
  • Guaranteed At Least Once
    • Outbox Support
    • Inbox Support
    • EFCore Outbox
    • Dapper Outbox
    • Dynamo Outbox
    • MSSQL Inbox
    • MySQL Inbox
    • Postgres Inbox
    • Sqlite Inbox
    • Dynamo Inbox
  • Darker Query Handlers and Middleware Pipelines
    • How to Implement a Query Handler
  • Health Checks and Observability
    • Logging
    • Monitoring
    • Health Checks
    • Telemetry
  • Command, Processors and Dispatchers
    • Command, Processor and Dispatcher Patterns
  • Under the Hood
    • How The Command Processor Works
    • How Service Activator Works
  • Event Driven Architectures
    • Microservices
    • Event Driven Collaboration
    • Event Carried State Transfer
    • Outbox Pattern
  • Task Queues
    • Using a Task Queue
  • FAQ
    • FAQ
  • END OF VERSION
    • Version Ends
  • VERSION 10
    • Version Begins
  • Overview
    • Show me the code!
    • Basic Concepts
    • Why Brighter?
  • Brighter Configuration
    • Basic Configuration
    • How Configuring the Command Processor Works
    • How Configuring a Dispatcher for an External Bus Works
    • RabbitMQ Configuration
    • AWS SNS Configuration
    • Kafka Configuration
    • Azure Service Bus Configuration
    • Azure Archive Provider Configuration
  • Darker Configuration
    • Basic Configuration
  • Brighter Request Handlers and Middleware Pipelines
    • Building an Async Pipeline of Request Handlers
    • Basic Configuration
    • How to Implement an Async Request Handler
    • Requests, Commands and an Events
    • Dispatching Requests
    • Dispatching An Async Request
    • Returning results from a Handler
    • Using an External Bus
    • Message Mappers
    • Routing
    • Building a Pipeline of Request Handlers
    • Passing information between Handlers in the Pipeline
    • Failure and Dead Letter Queues
    • Supporting Retry and Circuit Breaker
    • Failure and Fallback
    • Feature Switches
  • Guaranteed At Least Once
    • Outbox Support
    • Inbox Support
    • EFCore Outbox
    • Dapper Outbox
    • Dynamo Outbox
    • MSSQL Inbox
    • MySQL Inbox
    • Postgres Inbox
    • Sqlite Inbox
    • Dynamo Inbox
  • Darker Query Handlers and Middleware Pipelines
    • How to Implement a Query Handler
  • Health Checks and Observability
    • Logging
    • Monitoring
    • Health Checks
    • Telemetry
  • Command, Processors and Dispatchers
    • Command, Processor and Dispatcher Patterns
  • Under the Hood
    • How The Command Processor Works
    • How Service Activator Works
  • Event Driven Architectures
    • Microservices
    • Event Driven Collaboration
    • Event Carried State Transfer
    • Outbox Pattern
  • Task Queues
    • Using a Task Queue
  • FAQ
    • FAQ
  • END OF VERSION
    • Version Ends
Powered by GitBook
On this page
  • Usage
  • Registering a Handler
  • Taking a Dependency on a Command Processor
  • Pipelines Must be Homogeneous
  • Dispatching Requests
  • Internal Bus: Send & Publish
  • External Bus: Post, Deposit and Clear

Was this helpful?

Edit on GitHub
  1. Brighter Request Handlers and Middleware Pipelines

Dispatching Requests

PreviousRequests, Commands and an EventsNextDispatching An Async Request

Last updated 1 year ago

Was this helpful?

Once you have , you will want to dispatch Commands or Events to that Handler.

Usage

In the following example code we register a handler, create a Command Processor, and then use that Command Processor to dispatch a request to the handler.

  public class Program
    {
        private static void Main()
        {
            var host = Host.CreateDefaultBuilder()
                .ConfigureServices((context, collection) =>
                {
                    collection.AddBrighter().AutoFromAssemblies();
                })
                .UseConsoleLifetime()
                .Build();

            var commandProcessor = host.Services.GetService<IAmACommandProcessor>();

            commandProcessor.Send(new GreetingCommand("Ian"));

            host.WaitForShutdown();
        }
   }

Registering a Handler

In order for the Command Processor to find a Handler for your Command or Event you need to register the association between that Command or Event and your Handler.

Taking a Dependency on a Command Processor

Producers

Typically, a producer is an ASP.NET WebAPI or MVC app. In this case you take a dependency in your Controller on the IAmACommandProcessor interface, which is satisfied via ServiceCollection.

If you intend to dispatch messages to another app, via message oriented middleware, your Brighter configuration will need a Publication which identifies how to to do that.

Consumer

An Internal Bus consumer is just a handler, typically registered through Brighter's ServiceCollection integration via our HostBuilder extension. It can thus take dependencies on other registered services within your app.

An External Bus consumer is just a handler, but typically you host it using Brighter's Service Activator. You configure Service Activator to listens to messages flowing over message oriented middleware through a Subscription. Service Activator takes care of listening to messages arriving via the middleware, and delivering them to your handler code. In this way the complexity of using middleware is abstracted away from you, and you can focus on the business logic in your handler that you want to run in response to a message.

Pipelines Must be Homogeneous

Brighter only supports pipelines that are solely IHandleRequestsAsync or IHandleRequests. In particular, note that middleware (attributes on your handler) must be of the same type as the rest of your pipeline. A common mistake is to UsePolicy when you mean UsePolicyAsync.

Dispatching Requests

Once you have registered your Handlers, you can dispatch requests to them.

Internal Bus: Send & Publish

When using an Internal Bus, the Command Processor has two options for dispatching messages:

  • Send: Used with a Command, send expects one, and only one, receiver.

  • Publish: used with an Event, publish expects zero or more receivers.

All methods have versions that support async...await.

Internal Bus: Sending a Command

A Command is an instruction to do work. We only expect one recipient to do the work, and side-effects mean that we want to ensure that only one receiver actions it as it typically mutates state.

To send a Command you simply use CommandProcessor.Send()

commandProcessor.Send(new GreetingCommand("Ian"));

NOTE: On a call to CommandProcessor.Send() the execution path flows to the handler. The Internal Bus is not buffered.

Internal Bus: Returning results of a Command to the caller.

Internal Bus: Publishing an Event

An Event is a fact, often the results of work that has been done. It is not atypical to raise an event to indicate the results of a Command having been actioned.

commandProcessor.Publish(new GreetingEvent("Ian has been greeted"));

NOTE: On a call to CommandProcessor.Publish() the execution path flows to all handlers in a loop. The Internal Bus is not buffered.

External Bus: Post, Deposit and Clear

  • Post: This is a one-step approach to dispatching a message via middleware. Use it if you do not need transactional messaging, as described above.

All methods have versions that support async...await.

In both cases, if you use an Outbox with external storage, the message will be eventually delivered if it is written to the Outbox, provided that you run an Outbox Sweeper to dispatch any messages in the Outbox that have not been marked as dispatched.

In this example we use CommandProcessor.Post() to dispatch a message over middleware.

commandProcessor.Post(new GreetingCommand("Ian"));

In this exaple, we use CommandProcessor.DepositPost() and CommandProcessor.ClearOutbox to raise a transactional message. We then immediately clear it to lower latency. (We could have relied on an Outbox Sweeper and you should have an Outbox Sweeper in case this was to fail).

In this example we are using Dapper as the library for writing our entities to the Db, and have used Brighter's Unit of Work support for that (passed into the handler constructor).

public override async Task<AddGreeting> HandleAsync(AddGreeting addGreeting, CancellationToken cancellationToken = default(CancellationToken))
{
    var posts = new List<Guid>();
    
    //We use the unit of work to grab connection and transaction, because Outbox needs
    //to share them 'behind the scenes'

    var conn = await _uow.GetConnectionAsync(cancellationToken);
    await conn.OpenAsync(cancellationToken);
    var tx = _uow.GetTransaction();
    try
    {
        var searchbyName = Predicates.Field<Person>(p => p.Name, Operator.Eq, addGreeting.Name);
        var people = await conn.GetListAsync<Person>(searchbyName, transaction: tx);
        var person = people.Single();
        
        var greeting = new Greeting(addGreeting.Greeting, person);
        
        //write the added child entity to the Db
        await conn.InsertAsync<Greeting>(greeting, tx);

        //Now write the message we want to send to the Db in the same transaction.
        posts.Add(await _postBox.DepositPostAsync(new GreetingMade(greeting.Greet()), cancellationToken: cancellationToken));
        
        //commit both new greeting and outgoing message
        await tx.CommitAsync(cancellationToken);
    }
    catch (Exception e)
    {   
        _logger.LogError(e, "Exception thrown handling Add Greeting request");
        //it went wrong, rollback the entity change and the downstream message
        await tx.RollbackAsync(cancellationToken);
        return await base.HandleAsync(addGreeting, cancellationToken);
    }

    //Send this message via a transport. We need the ids to send just the messages here, not all outstanding ones.
    //Alternatively, you can let the Sweeper do this, but at the cost of increased latency
    await _postBox.ClearOutboxAsync(posts, cancellationToken:cancellationToken);

    return await base.HandleAsync(addGreeting, cancellationToken);
}

Message Mapper, MT_COMMAND and MT_EVENT

Given you may have both a Command and an Event how do we preserve that behavior (a command expects one handler, an event zero or more) in listening applications?

By setting the Message.MessageType to MT_COMMAND or MT_EVENT you indicate whether you expect this message to be treated as a Command or an Event. We flow that information in the message headers when sending over middleware.

When Service Activator listens to messages it expects that the MessageType matches the type of IRequest, either Command or Event that your message mapper code transforms the message into. It will then use CommandProcessor.Send() to dispatch messages to a single handler, or CommandProcessor.Publish to dispatch messages to zero or more handlers, as appropriate.

Brighter's HostBuilder support provides AutoFromAssemblies to register any Request Handlers in the project. See for more. If you are not using HostBuilder and or ServiceCollection you will need to register your handlers yourself. See .

Brighter follows Command-Query separation, and a Command does not have return value. So CommandDispatcher.Send() does not return anything. Please see a discussion on how to handle this in . Also note that Darker provides our support for a Query over an Internal Bus.

When using an the Command Processor has two options for dispatching a message:

DepositPost and ClearOutbox: This is a two-step approach to dispatching a message via middleware. It allows you to include the DepositPost call that puts the message in your within a database transation, so that you can achieve transactional messaging (either the message is placed in the Outbox and the change is made to any entities, or nothing is written to either).

When sending a message, the is invoked to map your request to a Brighter Message which can be sent over message oriented middleware.

implemented your Request Handler
Basic Configuration
How Configuring the Command Processor Works
Returning Results from a Handler
External Bus
Outbox
Message Mapper