Exchanging Messages

Overview

In the SOA world, services interact through the exchange of messages.  A service can declare the message exchange pattern that it supports for a given operation, which defines the order, direction, and cardinality of messages it will exchange with consumers.  In SwitchYard, this concept of message exchange is formalized in the Exchange interface.  Exchange provides a conduit for the messages that flow into and out of a service as part of a service invocation.  Two examples of a message exchange can be found below:

 

exchange-meps.png

As messages are sent and received, they need to be processed by the service consumer and provider.  SwitchYard provides an ExchangeHandler interface that consumers and providers can use to introduce processing logic for messages and faults generated as part of a service invocation.  Unlike messages, an exchange cannot be copied and reused across service   invocations.  State associated with an invocation (i.e. context) is   maintained at the exchange level.

 

Requirements

  • Provide a communication medium for exchanging messages and context as part of a service invocation.
  • Support the following message exchange patterns: 
    • InOnly (a.k.a one-way)
    • InOut (a.k.a. request-response)
    • OutOnly (a.k.a. notification)
  • Provide a mechanism for context to be scoped according to its relevance/lifecycle.

  • Each exchange must have a unique, system-generated identifier.
  • Exchange instances must be serializable.
  • Service providers must be able to associate service handling logic with a registered service.
  • Service consumers must be able to specify response handling logic in cases where a service returns a message.
  • Any number of handlers (0 ... *) can be associated with an exchange.
  • All events in an exchange are eligible to be handled.
  • A handler must be able to stop propagation of an event.

 

 

Creating Exchanges

 

Exchange instances are created via the following factory methods:

 

Exchange createExchange(QName service, ExchangePattern pattern);
Exchange createExchange(QName service, ExchangePattern pattern, ExchangeHandler handler);

 

  • The 'service' parameter specifies the service to be invoked by the Exchange, which is an immutable property of the exchange.
  • The  'pattern' parameter specifies the message exchange pattern for the  created exchange, which is an immutable property of the exchange..
  • The 'handler' parameter is used if the client expects a reply message (e.g. Fault, Out).  The specified handler is used to process any reply  messages sent as part of this exchange.

 

Exchanges returned from either factory method contain an immutable,  system-generated identifier that uniquely identifies the exchange  instance.

 

 

Handling Exchanges

The role of a Handler is to handle the  events that are generated during the  course of a message exchange.   When consuming a service, a handler can be registered during Exchange creation to handle response messages and faults.  When providing a service, a handler is specified while registering the service.

 

The first step to handling an event is to implement the ExchangeHandler interface.

 

public interface ExchangeHandler {
    void handleMessage(Exchange exchange) throws HandlerException;
    void handleFault(Exchange exchange);
}

 

Once you have your handler implementation, it needs to be registered.  Handlers can be set when consuming or providing a service:

 

// Consumer handler

Exchange createExchange(QName service, ExchangePattern pattern, ExchangeHandler handler);

// Provider handler

Service registerService(QName serviceName, ExchangeHandler handler);

 

There  are no restrictions on how many times a handler implementation can be used and for what.  It's quite likely that service components will have  a  'stock' handler instance which is used for all exchanges.  Thread safety is an important consideration for handler implementations that   are shared across multiple exchanges and any handler implementation that is registered with a service.

 

The default  behavior of an ExchangeHandler implementation is to handle all message states in both directions (send, receive).  If you are only interested  in an subset of these events, you can add logic to your handler implementation or extend the BaseHandler class:

 

public class BaseHandler implements ExchangeHandler {


    public void handleFault(Exchange exchange) {
        // NOP
    }

    public void handleMessage(Exchange exchange) throws HandlerException {
        // NOP
    }
}

 

 

Extending BaseHandler allows you to opt in for the messages you care about by selectively overriding BaseHandler methods.  For example, if you have an error processing handler, you could extend BaseHandler and override handleFault().

 

The  current implementation of BaseHandler provides a NOP implementation  for all events.  It may be desirable to make this behavior a bit more  flexible for common use cases (e.g. store any unhandled messages in the message store).  This could be handled via configuration and/or  through other stock implementations of ExchangeHandler.  Configuration would be more desirable as it allows the behavior to be changed as  needed.

 

Message Exchange

 

The following code illustrates how a servcie consumer creates an exchange, sends a request message, and processes the response:

 

// Create a handler to deal with response messages for the exchange

ExchangeHandler consumer = new BaseHandler() {
     public void handleMessage(ExchangeOutEvent event) throws HandlerException {

          outEvents.add(event);
     }

};

// Create an exchange for an in-out service and supply a response handler
Exchange exchange = _domain.createExchange(serviceName, ExchangePattern.IN_OUT, consumer);
// Send the request message, which kicks off the exchange
exchange.sendIn(MessageBuilder.newInstance().buildMessage());

 

The following code illustrates how a service provider responds to a invocation request message:

 

// Create a handler instance to handle request messages and generate a reply

ExchangeHandler provider = new BaseHandler() {
     public void handleMessage(ExchangeInEvent event) throws HandlerException {
          try {

               Message outMsg = MessageBuilder.newInstance().buildMessage();
               event.getExchange().sendOut(outMsg);
          } catch (Exception ex) {
               Assert.fail(ex.toString());
          }
}

 

// Register the service by name and provide a reference to the handler

_domain.registerService(serviceName, provider);