2

Using NServiceBus 4.3 I want to send a message to the error queue when certain conditions arise.

The scenario is that when I get a message in I check if this message is referring to 1 or more items in our database. If there are multiple references I throw a AmbiguousItemException and catch it. I need to email the person responsible for giving me the correct information. All of that is figured out but I don't want this message to be tried again. Instead I'd rather move it to the error Queue so when we get back the information we need we can add in the nullable property and put the message back into the queue for processing. I've tried using _bus.ForwardCurrentMessageTo("error"), _bus.Send("error", message), _bus.SendLocal(message). The last one basically puts the message in an infinite loop. The code is kind of like this.

public class MoveToErrorQueue
{
    private readonly IBus _bus;

    public MoveToErrorQueue(IBus bus)
    {
        _bus = bus;
    }

    public virtual void Send(ResubmitMessage message)
    {
        message.Foo= -1;
        _bus.Send("error", message);
    }
}

and the code that calls it

        try
        {
            //removed for brevity
        }
        catch (AmbiguousItemException ex)
        {
            Log.Error(ex);
            sendNotificationCommand.FailureMessage = ex.Message;
            _moveToErrorQueue.Send(commandMesage);
        }
        SendNotification(sendScanningNotificationCommand);
Robert Snyder
  • 2,399
  • 4
  • 33
  • 65

2 Answers2

6

From what you are describing it sounds like you have a long running business process. This is a candidate for using a Saga. Sagas handle incoming messages like a handler does but the Saga also allows you to track state. So rather than try to kick your message off into an error queue (which is not really a good idea) you can instead set a flag of some type like a boolean or enum on the Saga which will indicate that you received a message that is "referring to 1 or more items in your database" as you put it.

After setting the flag you can then fire off some kind of message to notify you or whoever that an email needs to be sent out to your customer to get updated info (this can probably be automated).

Once you receive the necessary info you can take whatever action is necessary and then send a message back to the Saga telling it to continue with its process and/or mark it as complete and close it out.

You can learn more about Sagas here

Colin Pear
  • 3,028
  • 1
  • 29
  • 33
  • So this probably would have worked, but after talking with the team we decided to not enqueue a notification command and to just let the message error out and move to the error queue. – Robert Snyder Jul 06 '16 at 13:34
  • 1
    A notification command can come right back to the Saga and it could handle it itself. That was just a suggestion too. You could just as easily put a record in a table or send an email to yourself etc. Cluttering up the error queue is not a recommended approach. Its not what it was designed for. I wouldn't recommend that. – Colin Pear Jul 06 '16 at 15:05
  • 2
    Just a note, I think @ColinPear means that the error queue shouldn't be the catch all for business validation errors. – Justin Self Jul 07 '16 at 19:14
  • Completely agree, the solution here is to have proper business logic in place to email the person responsible. This likely results in a saga, as @ColinPear suggests. – Dennis van der Stelt Jul 11 '16 at 14:47
1

It is possible to plug into the retires API and return a "no retries" number to essentially send certain exceptions to the error queue

http://docs.particular.net/nservicebus/errors/automatic-retries#second-level-retries-custom-retry-policy-exception-based-policy

var retriesSettings = busConfiguration.SecondLevelRetries();
retriesSettings.CustomRetryPolicy(MyCustomRetryPolicy);

The Policy

TimeSpan MyCustomRetryPolicy(TransportMessage transportMessage)
{
    if (transportMessage.ExceptionType() == typeof(MyBusinessException).FullName)
    {
        // Do not retry for MyBusinessException
        return TimeSpan.MinValue;
    }

    if (transportMessage.NumberOfRetries() >= 3)
    {
        return TimeSpan.MinValue;
    }

    return TimeSpan.FromSeconds(5);
}

And a header helper

static class ErrorsHeadersHelper
{
    internal static int NumberOfRetries(this TransportMessage transportMessage)
    {
        string value;
        if (transportMessage.Headers.TryGetValue(Headers.Retries, out value))
        {
            return int.Parse(value);
        }
        return 0;
    }

    internal static string ExceptionType(this TransportMessage transportMessage)
    {
        return transportMessage.Headers["NServiceBus.ExceptionInfo.ExceptionType"];
    }
}
Simon
  • 33,714
  • 21
  • 133
  • 202
  • This should work, but perhaps it should be noted that the FLR will still run through the configured number of times before handing it over to the SLR. I don't know if you can plug into the pipeline while the FLR is doing it's thing. As I understand it, you don't get the headers added until the FLR has exhausted its attempts. – Justin Self Jul 07 '16 at 19:17
  • @JustinSelf correct. do you have a requirement to control FLR based on exception type? if so i could look into it and add a sample if it is possible – Simon Jul 08 '16 at 02:03