In this article we're going to have a look at the request/response patterns available in Nimbus.

We've already seen Command handling with Nimbus and Eventing with Nimbus about command and event patterns respectively; now it's time to take a look at the last key messaging pattern you'll need to understand: request/response.

To get this out of the way right up-front, let's be blunt: request/response via a service bus is the subject of religious wars. There are people who argue adamantly that you simply shouldn't do it (possibly because their tools of choice don't support it very well ;) ) and there are others who are in the camp of "do it but use it judiciously". I'm in the latter. Sometimes my app needs to ask someone a question and wait for a response before continuing. Get over it.

Anyway, down to business.

The first item on our list is a simple request/response. In other words, we ask a question and we wait for an answer. One key principle here is that requests should not change the state of your domain. In other words, requests are a question, not an instruction; a query, not a command. There are some exceptions to this rule but if you're well-enough versed in messaging patterns to identify these (usually but not exclusively the try/do pattern) then this primer really isn't for you.

Let's take another look at our inspirational text messaging app. If you're not familiar with it, now would be a good time to have a quick flick back to the previous two posts in the series. Go ahead. I'll wait :)

So a customer has just signed up for our inspirational text message service and we're in the process of taking a payment. Our initial payment processing code might look something like this:

public async Task BillCustomer(Guid customerId, Money amount)
{
    await _bus.Send(new BillCustomerCommand(customerId, amount));
}

and our handler code might look something like this:

public class BillCustomerCommandHandler: IHandleCommand<BillCustomerCommand>
{

    ...

    [PCIAuditable]
    public async Task Handle(BillCustomerCommand busCommand)
    {
        var customerId = busCommand.CustomerId;
        var amount = busCommand.Amount;

        var creditCardDetails = _secureVault.ExtractSecuredCreditCardDetails(customerId);

        var fraudCheckResponse = await _bus.Request(new FraudCheckRequest(creditCardDetails, amount));

        if (fraudCheckResponse.IsFraudulent)
        {
            await _bus.Publish(new FraudulentTransactionAttemptEvent(customerId, amount));
        }
        else
        {
            _cardGateway.ProcessPayment(creditCardDetails, amount);
            await _bus.Publish(new TransactionCompletedEvent(customerId, amount));
        }
    }
}

So what's going on here? We can see that our handler plucks card details from some kind of secure vault (this isn't a PCI-compliance tutorial but nonetheless please, please, please don't pass credit card numbers around in the clear) and performs a fraud check on the potential transaction. The fraud check could involve the number of times we've seen that credit card number in the past few minutes, the number of different names we've seen associated with the card, the variation in amounts... the list is endless. Let's assume for the sake of this scenario that we have a great little service that just gives us a boolean IsFraudulent response and we can act on that.

Scenario #1: Single fraud-checking service

Single fraud-checking service

In this scenario we have our app server talking to our fraud-checking service. We'll ignore our web server for now. It still exists but doesn't play a part in this scenario.

This is actually pretty straight-forward: we have one app server (or many; it doesn't matter) asking questions and one fraud-checking service responding. But, as per usual, business is booming and we need to scale up in a hurry.

Scenario #2: Multiple fraud-checking services

Multiple fraud-checking services

We've already done pretty much everything we need to do to scale this out. Our code doesn't need to change; our requestor doesn't need to know that its requests are being handled by more than one responder and our responders don't need to know of each others' existence. Just add more fraud checkers and we're all good.

Only one instance of a fraud checker will receive a copy of each request so, as per our command pattern, we get load-balancing for free.

Scenario #3: Multicast request/response (a.k.a. Black-balling)

Black-balling

Let's now say that we want our fraud checking to take a different shape. We don't have a single fraud-checking service any more; we have a series of different fraud checkers that each do different things. One might do a "number of times this card number has been seen in the last minute" check and another might do an "Is this a known-compromised card?" check.

In this scenario, we might just want to ask "Does anybody object to this transaction?" and let different services reply as they will.

The first cut of our billing handler could now look something like this:

public class BillCustomerCommandHandler: IHandleCommand<BillCustomerCommand>
{

    ...

    [PCIAuditable]
    public async Task Handle(BillCustomerCommand busCommand)
    {
        var customerId = busCommand.CustomerId;
        var amount = busCommand.Amount;

        var creditCardDetails = _secureVault.ExtractSecuredCreditCardDetails(customerId);

        var fraudCheckResponses = await _bus.MulticastRequest(new FraudCheckRequest(creditCardDetails, amount),
                                                              TimeSpan.FromSeconds(1));

        if (fraudCheckResponses.Any())
        {
            await _bus.Publish(new FraudulentTransactionAttemptEvent(customerId, amount));
        }
        else
        {
            _cardGateway.ProcessPayment(creditCardDetails, amount);
            await _bus.Publish(new TransactionCompletedEvent(customerId, amount));
        }
    }
}

Let's take a closer look.

var fraudCheckResponses = await _bus.MulticastRequest(new FraudCheckRequest(creditCardDetails, amount),
                                                      TimeSpan.FromSeconds(1));

This line of code is now fetching an IEnumerable<FraudCheckResponse> from our fraud checking services rather than a single response. We're waiting for one second and then checking if there were any responses received. This means that we can now use a "black-ball" style pattern (also known as "speak now or forever hold your peace") and simply allow any objectors to object within a specified timeout. If nobody objects then the transaction is presumed non-fraudulent and we process it as per normal.

One optimisation we can now make is that we can choose to take:

  1. The first response.
  2. The first n responses.
  3. All the responses within the specified timeout.

In this case, a slightly tidied version could look like this:

var isFraudulent = await _bus.MulticastRequest(new FraudCheckRequest(creditCardDetails, amount),
                                                TimeSpan.FromSeconds(1))
                             .Any();

if (isFraudulent)
{
    await _bus.Publish(new FraudulentTransactionAttemptEvent(customerId, amount));
}
else
{
    _cardGateway.ProcessPayment(creditCardDetails, amount);
    await _bus.Publish(new TransactionCompletedEvent(customerId, amount));
}

Note the call to .Any(). Nimbus will opportunistically return responses off the wire as soon as they arrive, meaning that your calls to Enumerator.GetNext() will only block until there's another message waiting (or the timeout elapses). If we're only interested in whether anyone replies, any reply is enough for us to drop through immediately. If nobody replies saying that the transaction is fraudulent then we simply drop through after one second and continue on our merry way.

We could also use some kind of .Where(response &eq;> response.LikelihoodOfFraud > 0.5M).Any() or even a quorum/voting system - it's entirely up to you.