There is an old principle: show don't tell, and this introduction is about showing you what you can do with Brighter and Darker. It's not about how - more detailed documentation elsewhere shows you how to write this code. It's not about why - articles elsewhere discuss some of the reasons behind this approach. It is just, let me see how Brighter works.
Brighter and Darker
Brighter is about Requests
A Request is a message sent over a bus. A request may update state.
A Command is an instruction to execute some behavior. An Event is a notification.
You use the Command Processor to separate the sender from the receiver, and to provide middleware functionality like a retry.
Darker is about Queries
A Query is a message executed via a bus that returns a Result. A query does not update state.
You use the Query Processor to separate the requester from the replier, and to provide middleware functionality like a retry.
Middleware
Both Brighter and Darker allow you to provide middleware that runs between a request or query being made and being handled. The middleware used by a handler is configured by attributes.
Sending and Querying Example
In this example, we show sending a command, and querying for the results of issuing it, from within an ASP.NET WebAPI controller method.
[QueryLogging(0)][RetryableQuery(1,Retry.EXPONENTIAL_RETRYPOLICYASYNC)]public override async Task<FindPersonsGreetings> ExecuteAsync(FindGreetingsForPerson query, CancellationToken cancellationToken = new CancellationToken())
{var sql =@"select p.Id, p.Name, g.Id, g.Message from Person p inner join Greeting g on g.Recipient_Id = p.Id";var people =await_uow.Database.QueryAsync<Person,Greeting,Person>(sql, (person, greeting) => {person.Greetings.Add(greeting);return person; }, splitOn:"Id");var peopleGreetings =people.GroupBy(p =>p.Id).Select(grp => {var groupedPerson =grp.First();groupedPerson.Greetings=grp.Select(p =>p.Greetings.Single()).ToList();return groupedPerson; });var person =peopleGreetings.Single();returnnewFindPersonsGreetings { Name =person.Name, Greetings =person.Greetings.Select(g =>newSalutation(g.Greet())) };}
Using an External Bus
As well as using an Internal Bus, in Brighter you can use an External Bus - middleware such as RabbitMQ or Kafka - to send a request between processes. Brighter supports both sending a request, and provides a Dispatcher than can listen for requests on middleware and forward it to a handler.
The following code sends a request to another process.
[RequestLogging(0,HandlerTiming.Before)][UsePolicyAsync(step:1, policy:Policies.Retry.EXPONENTIAL_RETRYPOLICYASYNC)]public override async Task<AddGreeting> HandleAsync(AddGreeting addGreeting, CancellationToken cancellationToken = default(CancellationToken))
{var posts =newList<Guid>(); //We use the unit of work to grab connection and transaction, because Outbox needs //to share them 'behind the scenes'var tx =await_uow.BeginOrGetTransactionAsync(cancellationToken);try {var searchbyName =Predicates.Field<Person>(p =>p.Name,Operator.Eq,addGreeting.Name);var people =await_uow.Database.GetListAsync<Person>(searchbyName, transaction: tx);var person =people.Single();var greeting =newGreeting(addGreeting.Greeting, person); //write the added child entity to the Dbawait_uow.Database.InsertAsync<Greeting>(greeting, tx); //Now write the message we want to send to the Db in the same transaction.posts.Add(await_postBox.DepositPostAsync(newGreetingMade(greeting.Greet()), cancellationToken: cancellationToken)); //commit both new greeting and outgoing messageawaittx.CommitAsync(cancellationToken); }catch (Exception e) { _logger.LogError(e,"Exception thrown handling Add Greeting request"); //it went wrong, rollback the entity change and the downstream messageawaittx.RollbackAsync(cancellationToken);returnawait 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 latencyawait_postBox.ClearOutboxAsync(posts, cancellationToken:cancellationToken);returnawait base.HandleAsync(addGreeting, cancellationToken);}
The following code receives a message, sent from another process, via a dispatcher. It uses an Inbox to ensure that it does not process duplicate messages