Chapter 8. Resilient Mule Applications

Any Mule application must be able to handle exceptional situations that can occur from time to time. This chapter will cover how you can manage and work with such exceptional situations.

Error Handling and Recovery

Error handling and recovery comes in various shapes and sizes and greatly depends on your application's needs. Developers can employ several techniques to handle exceptions.

One way is to log the error message with the appropriate information. Additionally, rather than merely sending the error to a log file, it could also be copied or sent to an alternative destination. This technique assumes that errors merely need to be flagged and that someone or something that can deal with errors is monitoring the destination. Even if the error will be handled automatically, logging the error is always useful when tracing a history of faults within a system.

Another method involves automatic troubleshooting, which implies that there is a planned solution for a predictable error condition. If such a condition can be coded and placed on the ESB, an application can sort out its own errors. Because such solutions would be precisely defined, the solution would not be appropriate if an exception varies slightly from what is expected.

Routing patterns involve applying one of the numerous exception routing patterns that are available to handle exceptions. Either of the previous two points—error logging or automatic troubleshooting—can be used within routing patterns.

Mule's Exception Strategy Classes

Mule has an abstract class, org.mule.AbstractExceptionListener, which is the basis for every exception strategy. This class has several methods that are of use, including those shown here:

  • rollbackTransaction() is called to roll back the current transaction when an exception is received.

  • handleLifecycleException() handles all exceptions thrown during an object's lifecycle, such as initializing, destroying, and so forth.

  • handleMessagingException() handles exceptions thrown during normal message processing.

  • handleRoutingException() handles exceptions thrown during message routing.

  • handleStandardException() handles any other type of exception.

  • routeException() routes the exception message to a given endpoint.

Mule's default exception strategy, org.mule.DefaultExceptionStrategy, grabs the exception, logs it into the default logger, and routes it to an alternative endpoint. There is also an exception strategy for services, called org.mule.service.DefaultServiceExceptionStrategy, which inherits from the DefaultExceptionStrategy but also knows which service raised the exception.

Configuring Exception Strategies

The DefaultServiceExceptionStrategy is configured on a model within the Mule configuration and has one attribute, enableNotifications which indicates whether Mule system notifications should be raised. By default this is set to true.

The DefaultServiceExceptionStrategy class has the following child elements:

  • commit-transaction lets you specify a comma-separated list of wildcard patterns that Mule will match against the name of the exception. If the exception name matches, the transaction will not be rolled back.

  • rollback-transaction lets you specify a comma-separated list of wildcard patterns that Mule will match against the name of the exception. If the exception name matches, the transaction will be rolled back.

  • outbound-endpoint is the endpoint that the exception message will be routed to.

All exception strategies have these attributes and child elements. If you create your own strategy you will need to use the <custom-exception-strategy> element that has its own class attribute which should refer to your class.

Flexible Exception Strategies

What is rather flexible about Mule's exception-handling mechanism (illustrated in Figure 8-1) is that there is a distinction between exceptions related to the business or routing logic, and exceptions raised due to faults within the underlying technology.

  • Service exceptions occur within your application's business logic. Because all business components are contained within a Mule model, an exception strategy can be placed on the model and used by all components that it hosts. It is important to note that these exception strategies will catch and handle any exceptions raised by the service, including any raised in the inbound or outbound routers.

  • Transport exceptions relate to the connectors and are different from business exceptions because they can be handled differently. They may also need to be routed differently. The technologies are all represented in connectors, so you can add exception strategies for any connector defined inside your Mule configuration. If there is a technology exception, you will not be able to read or write to this sort of endpoint; Mule considers this a fatal condition.

Flexible Exception Strategies

Figure 8.1. Flexible Exception Strategies

Service Exception Strategies

Service exception strategies need to handle errors that occur within the business logic. This could be some kind of logical error but is more likely to be an error within the business process. Think about a passenger with a valid airline ticket whose record isn't in the system. He cannot be checked in but his ticket is authentic, so what should be done?

This exception strategy will handle all exceptions that are raised at any point in the message flow, that is, in the inbound flow stage, the component flow stage, or the outbound flow stage. As you can see in the example that follows, the DefaultServiceExceptionStrategy can be configured on a model before all the services or within a single service after the outbound router collection:

<model name="myModel">
   <default-service-exception-strategy>
      <outbound-endpoint address="jms://Error.queue"/>
   </default-service-exception-strategy>
   ...
   <service name="anyService">
      ...
      <default-service-exception-strategy>
        <outbound-endpoint address="jms://Other.error"/>
      </default-service-exception-strategy>
   </service>

</model>

Any exception that occurs within any of these services will be routed to the JMS endpoint called Error.queue, but exceptions raised within the service called anyService will be routed to the JMS endpoint called other.error.

Transport Exceptions

Transport exceptions are thrown by transports or transformers when there is a problem with the underlying technology, for example with reading data off an endpoint or with writing data to an endpoint. These exceptions are fatal exceptions; if there is a problem receiving or dispatching from/to a transport, the whole message flow fails. A service exception would only be raised for one message—all others will continue as normal.

Note

You should make sure to take care of these exceptions; otherwise an unexpected exception will halt your Mule server.

You can configure the exception strategy to use the DefaultConnectorExceptionStrategy class inside the connector, as shown here:

<vm:connector name="myConnector">
     <default-connector-exception-strategy>
      <outbound-endpoint address="stdio://OUT"/>
   </default-connector-exception-strategy>
</vm:connector>

Exception Strategies—Create Your Own

If you want to create your own exception strategies, you can extend the DefaultExceptionStrategy to perform your own routing and error handling. This implements all the abstract methods from the AbstractExceptionListener class, so it is a better place to start from.

You can choose to override any of the standard methods that handle the different exception types.

Note

Your code must contain the exception, should never itself throw an exception, and that fatal exceptions must also be managed.

Routing Patterns—Exception Router

One of the patterns from the EIP book is an exception-based router. This router is configured with two or more endpoints and will route along the first one that does not fail. In Mule it is implemented as <exception-based-router> and can be configured as shown here:

<outbound-router>
   <exception-based-router">
      <tcp:outbound-endpoint
            host="10.192.111.10" port="8080"/>
      <tcp:outbound-endpoint
host="10.192.111.11" port="8081"/>
      <jms:outbound-endpoint queue="pendingItems"/>
   </exception-based-router>
</outbound-router>

In this case, Mule will attempt to route the message along the first endpoint. If it cannot, any exception strategies associated with this transport will be activated, but if a FatalConnectException is raised, it will try routing it on the second endpoint. This is repeated until all of the endpoints have been tried, at which point an org.mule.api.routing.RoutingException is thrown.

Transactions in Mule

While we can cater for unexpected situations, we know that if an error occurs during a process, any data from queues or databases may need to be restored to leave the system in a consistent state. Transactions let us do this; we will see what sorts of transactions we can use in Mule in this section.

A transaction is generally described as a unit of interaction that guarantees the integrity of a data source. This can be a database, therefore referred to as a JDBC transaction, or it can be a queue, as in the case of a JMS transaction.

The lifecycle of a transaction inside an ESB consists of keeping track of the message flows that are meant to participate in the transaction once it has started. If any errors occur the entire transaction must be rolled back. If there are no errors all changes should be committed.

The key to all this is determining where the transaction boundaries lie, that is, where a transaction should (and can) start and where it should end.

Single vs. XA Transactions

Mule supports single-resource transactions and therefore natively supports JDBC and JMS transactions. Because the VM transport also has transaction support, you can use this to simulate transactionality while you are modeling your application. Apart from this, Mule also supports XA transactions, which allow you to start a transaction in one transport and end it in another.

Mule's transaction framework is independent of the underlying technology, so single-resource and multiple-resource transactions are all treated in the same way, making configuration easier.

The endpoints need to be configured for transactionality since data is consumed from, or sent to, the underlying technology on/from endpoints. In each case we can configure whether a new transaction needs to start on an endpoint or whether the endpoint should join an existing one.

The Transaction Manager

Every Mule application needs to have a transaction manager that controls the transactions within Mule. This is defined within <transaction-manager> XML elements and should be declared before the connectors. The transaction-manager element has two attributes:

  • name, which defaults to transactionManager, but which you can optionally set to any name.

  • factory, which is used to refer to a specific transaction class.

If Mule is deployed on an application server (for example JBoss or WebSphere), you can, and in some cases, must, use the transaction manager provided by the application server. Mule provides a series of transaction-manager classes that are specific to each application server, as listed here:

  • <websphere-transaction-manager/>

  • <jboss-transaction-manager/>

  • <weblogic-transaction-manager/>

  • <jrun-transaction-manager/>

  • <resin-transaction-manager/>

If Mule is not deployed on an application server you can use the default JBoss transaction manager (formerly called Arjuna) by including a declaration for it before the Mule model.

<jboss-transaction-manager/>

Transactional Endpoints

Endpoints can be made transactional by adding the appropriate <transaction> element to an <endpoint> tag. Valid transaction elements are as follows:

  • <jms:transaction> refers to a JMS transaction.

  • <jdbc:transaction> refers to a JDBC transaction.

  • <jms-client-ack-transaction> is a wrapper around the message acknowledgment feature of JMS. Rollback is not supported, but it is useful when consuming items off a JMS destination.

  • <vm:transaction> refers to a VM transaction.

These items have two attributes:

  • action tells Mule what to do for each Mule message received. This instruction can be any of the following:

    • NONE: Never participate in a transaction.

    • ALWAYS_BEGIN: Always start a new transaction at this point. If a transaction already exists and is in progress, raise an exception.

    • BEGIN_OR_JOIN: Join a transaction if one has started; otherwise create a new one.

    • ALWAYS_JOIN: Always join a transaction, as one should have started. If there is no transaction, raise an exception.

    • JOIN_IF_POSSIBLE: If a transaction is in progress, join it; otherwise continue as normal.

  • timeout refers to the length of time, in milliseconds, that Mule should wait before rolling back the transaction if it isn't complete.

Examples

The examples that follow demonstrate how transactions are configured on endpoints. This first example is a JMS transaction:

<jms:inbound-endpoint queue="test.In">
   <jms:transaction action="ALWAYS_BEGIN"
      timeout="60000"/>
</jms:inbound-endpoint>

Every message read from the test.In JMS queue will be contained inside a single-resource transaction that will time out after 60 seconds unless it is committed.

In this second example every record retrieved by the getOrder SQL query will join an existing JDBC transaction:

<jdbc:inbound-endpoint queryKey="getOrder?type=2">
       <jdbc:transaction action="ALWAYS_JOIN"/>
</jdbc:inbound-endpoint>

Normally, transactions are configured on the inbound endpoint, since this is the first point where data is consumed from queues or databases. This consumption would need to be rolled back to maintain consistency. A service's outbound endpoints will automatically join the current transaction if there is one, so there is no need to set any transaction elements. Multiple outbound endpoints, for example in a MessageSplitter outbound router, will all participate in the transaction if they can. However, if your transaction boundary starts on an outbound endpoint, the configuration shown previously can be used there instead.

Warning

If a message is inside a transacted message flow and it gets directed to a nontransactional endpoint, surprise timeouts may occur since there may not be a valid response even if there was no error.

XA Transactions

XA transactions allow single transactions to span multiple resources. In Mule this means JMS and/or JDBC and/or VM. The configuration is similar to the previous one, but instead of using a JMS transaction element you need to use the JMS XA transaction element (or a JDBC XA transaction element or a VM XA transaction element). You need to ensure that your transaction manager is XA-enabled; otherwise this will cause runtime problems.

If you compare the following configuration example to the example in the previous section, you will see how similar the configurations are:

<inbound-endpoint address="jms://my.queue">
   <jms:xa-transaction action="ALWAYS_BEGIN"
         timeout="60000"/>
</inbound-endpoint>

At the time of writing, one of the proposed changes for the next version of Mule (2.1) is to move the xa-transaction element into the mule namespace.

Poison Messages

If you read a message from a queue within a transaction and an exception occurs, the whole transaction is rolled back and the message is placed back on to the queue. This behavior is as expected, but what if the message itself caused the exception? In this case it will cause the exception again as soon as it is re-read and processed. Such a message is referred to as a "poison message."

Since exceptions can be caused by a number of issues, not just poison messages, you can configure how many times you want this rollback-and-read pattern to be invoked by adjusting the value of the maxRedelivery attribute on the JMS connector. It defaults to zero, so at the first exception the message will not be re-read by the connector.

Connection Strategies

Connection strategies provide the means, when writing or configuring transports, to specify the action Mule should take when message delivery fails due to an issue with the transport/connector. When defining connection strategies you can specify properties or attributes that determine factors such as the number of times Mule should attempt to reconnect, the time interval between retries before giving up, and the action to take on failure.

Connection strategies are included as a standard feature in the Mule 1.x Community Edition; however, they have been not been implemented in the Mule 2.0 Community Edition and will appear as a feature available only in the Enterprise Edition.

Summary

In this chapter we've looked at ways to make your Mule applications more resilient.

First we saw how exceptions can be handled in Mule as well as routed to a destination for further processing. This is preferable to letting Mule fail when it encounters exceptions that it does not know how to handle.

Exception strategies can handle different types of exceptions differently, so there is no need to have one single mechanism to cater for all possibilities. The basic behavior inside the DefaultExceptionStrategy is to route the exception message to an endpoint and log the exception into the log file. The DefaultServiceExceptionStrategy does the same thing, but is also aware of which service raised the exception. All of these strategies can be configured within a model to cater for all services within the model, or within a connector to cater for technology exceptions. Should the default behavior not suffice, you can extend the default classes and build your own.

Transactions, on the other hand, are there to guarantee integrity of the data that's being used. Mule supports JMS and JDBC transactions and also allows the use of XA transactions to span multiple resources. Additionally, VM can be included in transactions so that you can model transactions before rolling out the live technologies.

Mule needs to be configured with a transaction manager that will manage the transaction; each endpoint will then need a transaction element to indicate how it should participate within a transaction. This is done on the inbound side as outbound endpoints will automatically participate in an existing transaction.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset