Errors as Business Information

Java Development News:

Errors as Business Information

By Antoine Borg

01 Dec 2007 | TheServerSide.com

Introduction

Any work on an integration project utilises a wide range of established patterns. Gregor Hohpe’s sterling work on collecting common patterns is an excellent reference point for anyone working in this field. While it is common to put these patterns together within your project, there also is value in grouping patterns that are themselves patterns.

Business Information

Whenever I work on an integration or Service-Oriented Architecture (SOA) project, one of the first few steps I take is to identify what constitutes information that the business needs to perform its processes. This business information is what is meant to flow from one service to another and unless I know what each process requires (or what it generates), there is no way to figure out how to make sure that the right process receives the right information at the right time. This business information is different from control information that also flows through systems. Control information can be system notifications or exception messages.

In fact, this is why exception handling is rarely considered when trying to identify business information but there are a number of situations were exceptions caused by business processes are not technical issues but business issues – more like “Blank Customer Number” rather than “Null Pointer Exceptions”. In these cases, the exception message is also business information and needs to be tackled differently to the manner in which exceptions are normally handled. For example, the fact that an exception was raised needs to be routed back to the producer rather than handled as a technical exception.

A simple use case would be a situation where a service performs validation of any inbound data before allowing it to reach other services. These validator services would need to reply to the original producer and inform it that an error occurred.

A Validator Service

The situation described above is shown in Figure 01 below.

Figure 1 : Messages are validated, processed and then returned to the original producer

The common approach to such a situation is to have a validator service so that the incoming message can be cleaned and prepared for processing. As you can see, the validator service acts as a gatekeeper and can clean or validate the information before the processor service gets its hands on it. On the other hand, the processor service is what will reply to the original consumer in a synchronous situation.

Data Flow

Assuming that the data is correct and passes whatever tests the validator service imposes, the normal data flow is shown in Figure 02.

 

Figure 2 : The steps taken to validate and process the message

The message is received by the inbound endpoint of the validator service (1) and is handled by the validator. A validated message is output (2) by the validator and passed on to the endpoint that the processor service is listening on. The processor service consumes this message (3) and replies back to the original producer with the processed message (4).

This can be represented in Mule using the source code in Listing 01.

<mule-descriptor name="validatorService" implementation=
"org.ricston.patterns.pattern01.Validator">
 <inbound-router>
  <endpoint address="vm://fromWherever"/>
 </inbound-router>
 <outbound-router>
  <router className="org.mule.routing.outbound.
    OutboundPassThroughRouter">
   <endpoint address="vm://forProcessor"/>
  </router>
 </outbound-router>
</mule-descriptor>

Listing 1: The validator service hosted in Mule

Here we have a <mule-descriptor> that hosts the validator as a service and listens on the VM endpoint called “fromWherever” and writes on to the “forProcessor” VM endpoint. The processor service just listens on “forProcessor”. The routers configured are merely simple pass-through routers that will not interfere with the message. The Java code for the validator service and the processor service can be found in Listings 02 and 03 respectively.

public class Validator

{
 public String validateData (String sourceData) throws MessagingException
 {
     if (sourceData.indexOf("X") == -1) {
         return sourceData;
     } else {
         throw new MessagingException (null, sourceData);
     }
 }
}

Listing 2 : The Validator class

public class Processor
{
    public String ProcessData (String theData) {
        return "Hello World, "+theData+" calling ... ";
    }
}

Listing 3: The Processor class

As you can see, we’re going to raise an exception if the input string contains an uppercase “x”.

If you use the Mule test case in Listing 04, you will notice that a valid message is handled by the validator and passed on to the processor service which, in turn, replies to the original producer.

    public final void testThePatternUsingValidData() throws Exception
    {
        // initialisation
        MuleClient client = new MuleClient ();
        String validText = "Text which passes";

        //Send valid data
        UMOMessage reply = client.send("vm://fromWherever", new
     MuleMessage (validText));
        assertNotNull (reply);
        assertNotNull (reply.getPayload());
        assertEquals ("Hello World, "+validText+" calling ... ",
    reply.getPayloadAsString());
    }

Listing 4: Testing the Mule configuration using valid data.

The Mule test case in Listing 05 shows that the exception that the validator raised is not directed to the producer at all but, at best, can be handled by any exception handler that is configured for this Mule server.

    public final void testThePatternUsingInvalidData() throws Exception
    {
        // initialisation
        MuleClient client = new MuleClient ();
        String invalidText = "TeXt which passes";

        //Send valid data
        UMOMessage reply = client.send("vm://fromWherever", new
 MuleMessage (invalidText));
        assertNotNull (reply);
        assertNotNull (reply.getPayload());
        assertEquals ("Hello World, "+invalidText+" calling ... ",
reply.getPayloadAsString());
    }

Listing 5: Testing the Mule configuration using invalid data.

In this particular case, the fact that there was an exception is something that needs to be recorded as part of the business process itself rather than handled as a technical error which needs someone’s attention only. The validator needs to reply to the client and point out that such an error occurred, but if it swallows all exceptions, it will not have anything to route to the processor.

The answer here is to use a filtering pattern so that valid data is sent to the processor and the normal flow can take place. The error is routed to a dummy (empty) service that is just there to cause the reply to bounce back to the client as shown in Figure 03.

Figure 3: The intended message flow.

Listings 06 and 07 show the modified code for the Validator service and the full Mule configuration respectively.

public class Validator

{
    public String validateData (String sourceData)
    {
        if (sourceData.indexOf("X") == -1) {
            return sourceData;
        } else {
            return ("ERROR: You have sent me invalid data");
        }
    }
}

Listing 6: The modified Validator service.

<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mule-configuration SYSTEM "http://mule.mulesource.org/dtds/mule-configuration.dtd">

<mule-configuration version="1.0" id=" Exceptions_are_Information">

 <!-- A console connector to interact with the user -->
 <connector name="streamFromUser" className="org.mule.providers.
stream.SystemStreamConnector">
  <properties>
   <property name="promptMessage" value="Please type out
your name (A capital X will cause an exception to
be raised): "/>
   <property name="outputMessage" value=" ** And the
result is: "/>
  </properties>
 </connector>

 <global-endpoints>
  <endpoint name="fromUser" address="stream://System.in"
connector="streamFromUser" type="receiver"/>
  <endpoint name="toUser" address="stream://System.out"
   connector="streamFromUser" type="receiver"/>
 </global-endpoints>

 <model name="SedaModel">
  <mule-descriptor name="validatorService" implementation="org.
ricston.patterns.pattern01.Validator">
   <inbound-router>
    <endpoint address="vm://fromWherever"/>
   </inbound-router>
   <outbound-router matchAll="false">
    <router className="org.mule.routing.outbound.
FilteringOutboundRouter">
     <endpoint address="vm://forBounce"/>
     <filter className="org.mule.routing.
filters.WildcardFilter" pattern="ERROR*"/>
    </router>
    <router className="org.mule.routing.outbound.
OutboundPassThroughRouter">
     <endpoint address="vm://forProcessor"/>
    </router>
   </outbound-router>
  </mule-descriptor>

  <mule-descriptor name="processorService" implementation="org.
ricston.patterns.pattern01.Processor">
   <inbound-router>
    <endpoint address="vm://forProcessor"/>
   </inbound-router>
  </mule-descriptor>

  <mule-descriptor name="bridgeService" implementation="org.
mule.components.simple.BridgeComponent">
   <inbound-router>
    <global-endpoint name="fromUser"/>
   </inbound-router>
   <outbound-router>
    <router className="org.mule.routing.outbound.
ChainingRouter">
     <endpoint address="vm://fromWherever"/>
     <global-endpoint name="toUser"/>
    </router>
   </outbound-router>
  </mule-descriptor>

  <mule-descriptor name="bounceService" implementation="org.
mule.components.simple.BridgeComponent">
   <inbound-router>
    <endpoint address="vm://forBounce"/>
   </inbound-router>
  </mule-descriptor>

 </model>
</mule-configuration>

Listing 7: The full Mule Configuration.

Use Cases

There are several reasons for using this pattern:

01 – Validate data received by a web service. Rather than expose your real web service to the world, have a validator component “clean” the data first and prevent any errors.

02 – Provide one single web service to the world. Rather than expose many web services, you can publish one web service and have it act as a message broker by routing the information to various internal services.

03 – Any situation where a failure within the service needs to be correctly routed back to the service’s client.

Routing Patterns

The routing pattern used within this business pattern is the Selective Consumer. This allows you to send a message to different endpoints based upon whatever the message itself contains. <a href=”http://www.mulesource.org/display/MULE/Filters#Filters-LogicFilters”>Mule allows you to filter based upon a number of criteria such as content or payload type</a>.

Interceptors Raising Exceptions

The other possibility would be to choose the interceptor pattern rather than placing the validation logic into an independent service. The interceptor is interesting since it gives you the ability to perform common behaviour across multiple services. In our case, we could easily encapsulate the validation code into an interceptor and apply this interceptor across any service which requires this particular type of information. An interceptor would change our architecture slightly and would look like Figure 03. The difference between the two approaches becomes clear when we throw exceptions into the equation.

If an interceptor raises an exception, the best option is to send the error message to the processor service. The processor needs to be designed to accept error messages that it may need to return. This design is not as flexible since you will need to re-code your processor service to handle the error messages or error types with the advent of new error types. The previous model allows for this to be done through the intelligent use of filters and routers within configuration instead.

Summary

While it is easy to dismiss any sort of control information as not being relevant to the business, every data flow and event within a Service Oriented Architecture has got to do with the integration project at hand. In some cases, like the ones listed above, control information has specific business significance and needs to be routed to a service’s consumer. While there is no direct mechanism inside Mule to convert control information to business information, this pattern allows you to do this.