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.
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.
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.
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.
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.
Comments