Error and Fault Handling

Version 4

    Overview

     

    As described previously, a service invocation consists of an exchange of messages described by a message exchange pattern (or MEP).  Lots of errors can occur during the course of a message exchange, but there are only three primary actors.  Each of these actors acts on a message exchange via an exchange handler:

     

    • The consumer of the service may associate a handler when creating an exchange
    • The provider of the service associates a handler when registering a service
    • The bus attaches multiple handlers to exchanges in order to handle system-level functionality (delivery, addressing, etc.)

     

    Since errors happen in the context of exchange handler processing, establishing a set of rules for how errors are processed from handlers provides the basis for a consistent error handling mechanism across SwitchYard runtime.  A thorough examination of error handling mechanics follows, but the error handling rules can be expressed simply as:

     

    • All errors are reported as faults
    • Handlers can set faults directly on an exchange or throw a HandlerException which will be mapped to a fault by the bus
    • All faults are handled.  If a fault handler is not specified by a consumer, the fault will be placed in an error store (see Fault Sink).

     

     

    Reporting an Error

     

    All errors encountered during the course of a message exchange are considered to be faults by SwitchYard.  There are two options available for  creating and delivering faults that result from errors : implicit fault handling and explicit fault handling.

     

    Explicit Fault Handling

     

    As the name suggests, this option allows a handler to be very explicit  about how the fault is created.  The handler logic is responsible for  creating the fault message, populating with relevant information, and  returning the fault back to the service consumer.  Here's an example of  what this might look like:

     

    public void handleMessage(Exchange exchange) {
        try {
            thisMethodThrowsAnException();
        }
        catch (Exception ex) {
            // create the fault message to return
            Message fault = MessageBuilder.newInstance(FaultMessage.class).buildMessage();
            fault.setContent(ex.getMessage());
            exchange.send(fault);
        }
    }

     

     

    Implicit Fault Handling

     

    A  simpler way to return a fault is to simply throw a HandlerException  from the exchange handler implementation.  The runtime will  automatically halt the current event and create a fault message using  the thrown exception as input.

     

    public void handleMessage(Exchange exchange) throws HandlerException {
        try {
            thisMethodThrowsAnException();
        }
        catch (Exception ex) {
            // sit back and let the bus do the work
            throw new HandlerException("your request message stinks!", ex);
        }
    }

     

     

    Explicit vs. Implicit

     

    Either option works for generating faults, but each approach has its sweet spots.  The explicit mechanism is best when you need complete control over how the fault message is generated and sent.  Examples of this include construcing a custom fault content model (not a String) or setting specific context properties for the fault.  The implicit mechanism is best when you don't want to worry about details of fault processing - just throw the exception and you're done.

     

    When Good Exchanges Go Bad

     

    Let's walk through a message exchange to see how errors are handled.

     

    in-send.png

     

    1) A service consumer invokes send() on a new exchange, triggering system handlers in the exchange's handler chain. A handler in the chain  encounters an error and throws a HandlerException.  Exchange delivery is halted and a fault is created and populated with details from the HandlerException.  The fault is passed back through the exchange's handler chain.  No further processing occurs on the exchange.

     

    in-recv.png

     

    2) The bus triggers the handler chain for the service provider with an exchange.  A handler in the chain fails while processing the  event.  At this point, the handler has two options:

     

    • Halt the event, create a fault message, and invoke send() on the exchange with the fault message.
    • Throw  a HandlerException.  In this case, the bus takes the responsibility to halt the exchange and generate a new fault with details from the HandlerException.

     

    Regardless of the option chosen, no further provider handlers are triggered.  The exchange is passed back to the consumer's handler chain for  processing.

     

    out-send.png

     

    3) The provider invokes send() on an exchange with a response (out) message which triggers the system handlers for the exchange.  If an error occurs in any exchange handlers, a HandlerException should be thrown.  A fault is created from the details of the HandlerException and returned to the consumer.

     

    out-recv.png

     

    4) At this point the exchange has essentially completed from a message  exchange standpoint and there is no appropriate handler for fault  events.  Errors generated by the consumer handlers are handed off to the  Fault Sink.