Monday, July 29, 2013

Out of the Box Support for RESTful Transactions

The latest release of narayana includes a couple of great additions to the REST-AT module that represent a step change in the usability of restful transactions. The work has been contributed by transactions' team member Gytis Trikleris.

Mark has previously blogged about why RESTful transactions are useful and our implementation of the draft REST-AT specification is steadily finding real world uses (within BlackTie for example). However, although using REST-AT is easier than building upon a full web services stack containing support for WS-Atomic Transaction, there are still barriers to overcome such as making sure the REST-AT coordinator is managed and installed correctly, ensuring the coordinator endpoint is deployed and the responsibilities of the service writer to conform to the specification are certainly not trivial. Gytis' work addresses both deficiencies.

REST-AT Now Installs as a Wildfly Extension

Wildfly extensions provide a mechanism for extending the functionality provided by the Wildfly application server. Installing the REST-AT coordinator via an extension not only means that the coordinator will be available to all client applications but it will also benefit from the same management and configuration enjoyed by all subsystems. The application developer no longer needs to be concerned with deploying the coordinator, with managing versions of the coordinator, with managing conflicts with other applications that require REST-AT, nor does he need to ensure that any dependencies such as JAX-RS support or web services are available and compatible. The feature is available for use now in the latest version 8.0.0.Alpha3 of wildfly.

Simple Interface for Participating in REST-AT Transactions

Until now the service developer has needed to understand and comply with the requirements of the REST-AT specification including the responsibility of listening for transaction completion requests on HTTP endpoints. This forces the developer to design his service as a web service. The new mechanism means that any Java component can join a REST-AT transaction without this burden and consequently it becomes isolated from the details of using REST-AT. Writing an application that uses REST-AT means that services can take part in transactions that can potentially span multiple services and servers.
So how does it work in practice? The service writer simply registers his service to receive callback notifications when the global transaction is ready to complete. Specifically, you will need to depend on the RTS subsystem by including a dependency on it in your application manifest (add Dependencies: org.jboss.narayana.rts to MANIFEST.MF) and pull in the RTS library when building your services. For maven users you will need:

    <dependency>
        <groupId>org.jboss.narayana.rts</groupId>
        <artifactId>restat-integration</artifactId>
        <version>5.0.0.M3</version>
        <scope>provided</scope>
    </dependency>

The application developer interface to the RTS subsystem is

    String enlist(String applicationId,
                  String participantEnlistmentURL,
                  Participant participant);
    void registerDeserializer(String applicationId,
                  ParticipantDeserializer deserializer);
    void reportHeuristic(String participantId,
                  HeuristicType heuristicType);

The participantEnlistmentURL in the enlist method corresponds to a running REST transaction which the service acquires during normal interactions with service clients. To register for completion callbacks the service writer registers an interface using the enlist method and passes in an implementation of Participant:

    public interface Participant {
        Vote prepare();
        void commit() throws HeuristicException;
        void commitOnePhase();
        void rollback() throws HeuristicException;
    }

Now when a service client terminates a transaction the services' callback methods will be invoked (by a REST-AT coordinator which may or may not be running locally since these are distributed transactions). It is interesting to note that the wildfly application server is a modular container so subsystems and applications run in their own class loaders. In the event of failures a recovery system will need to recreate participant callback registrations in order to complete any pending transaction and therefore will no longer have access to the original class. The service writer must help the recovery system in this task via the registerDeserializer call to the RTS subsystem. The final method on the interface to RTS (reportHeuristic) is to allow services to independently abort or commit work before being asked to via the callback interface.

What's next

I think you will agree that this is a pretty simple, low overhead, interface that allows services to participate in global transactions and we encourage users to start experimenting with it. In forthcoming narayana snapshot releases we will be adding quickstarts and examples that showcase the functionality including how it is used in a J2SE environment. If you would like to try out the quickstarts now then go to our narayana quickstarts git repository and look for the service2 and recovery2 examples. We will also be providing more documentation of REST-AT features in the documentation repository.



In addition Gytis Trikleris will soon be integrating some more exciting work that he has been doing in this area: currently, if a service takes part in a REST-AT transaction which also starts JTA transactions then the two are not related, i.e. terminating the REST-AT transaction has no effect on the JTA transactions (and vice versa). Gytis has written a bridge that fixes this deficiency, "watch this space" as they say.

Thursday, July 4, 2013

Compensating Transactions: When ACID is too much (Part 4: Long Lived Transactions)

In part one, I explained how ACID transactions can have a negative impact on applications whose transactions can take a relatively long time to run. In addition, another potential issue with ACID transactions is that the failure of one unit can cause the entire transaction to be rolled back. This is less of an issue for short running transactions, as the previously successful work can be retried quickly. However, for long running transactions, this previously completed work may be significant, leading to a lot of waste should it need to be rolled back. In this post, I'll show how a compensation-based transaction could be a better fit for long lived transactions.

A compensation-based transaction can be composed of multiple short-lived ACID transactions. When each transaction completes, it releases the locks on the resources it held, allowing other transactions, requiring those resources, to proceed. A compensation action is registered for each ACID transaction and can be used to undo any work completed, should the entire compensation-based transaction need to be aborted. Furthermore, should one of these short-lived ACID transactions fail, it could be possible to find an alternative, preventing the entire transaction from failing. This allows forward progress to be achieved. By composing the compensation-based transaction as several units of work, you also gain the opportunity to selectively abort (compensate) particular units as the compensation-based transaction progresses. A simple example should help to clarify these ideas...

For example, take a travel booking scenario. We begin by booking a flight. We then try to book a taxi, but that fails. At this point we don’t want to compensate the flight as it may be fully-booked next time we try. Therefore we try to find an alternative Taxi, which in this example succeeds. Later, in the compensation-based transaction, we may find a cheaper flight, in which case we want to cancel the original flight whilst keeping the taxi and the cheaper flight. In this case we notify our intentions to the transaction manager who ensures that the more expensive flight is compensated when the compensation-based transaction completes.

Code Example

In this example, I expand on the Travel Agent example from part 3 in this series. Here I will show how a failure to complete one unit of work does not have to result in the whole compensation-based transaction being aborted.

public class Agent {
 
 @Inject
 HotelService hotelService;
 
 @Inject
 Taxi1Service taxi1Service;
 
 @Inject
 Taxi2Service taxi2Service;
 
 @Compensatable
 public void makeBooking(String emailAddress, String roomType, String destination) throws BookingException {
 
  hotelService.makeBooking(roomType, emailAddress);
  
  try {
   taxi1Service.makeBooking(destination, emailAddress);
  } catch (BookingException e) {
    /**
     * Taxi1 rolled back, but we still have the hotel booked. We don't want to lose it, so we now try Taxi2
     */
     taxi2Service.makeBooking(destination, emailAddress);
  }
 }
}

For this example, you can imagine that the Hotel and Taxi services are implemented similarly to the HotelService in part 3.
The makeBooking method is annotated with @Compensatable, which ensures that the method is invoked within a compensation-based transaction. The method begins by making a Hotel reservation. If this fails, we don't handle the BookingException, which causes the compensation-based transaction to be canceled. We then move onto booking a taxi. If this particular booking fails, we catch the BookingException and try an alternative Taxi company. Because the Taxi service failed immediately, we know that it should (it's a requirement of using this API) have undone any of it's work. We can therefore chose to fail the compensation-based transaction or, in this case try an alternative Taxi company. The important thing to note here is that we still have the Hotel booked and we don't really want to lose this booking as the hotel may be fully booked next time we try. The code then goes on to attempt an alternative Taxi company. If this booking fails, we have no option but to cancel the whole transaction as we have no other alternatives.

Conclusion

In this blog post, I showed how a compensation-based transaction could be a good fit for long running transactions. In the next part I'll discuss the status of our API for compensation-based transactions.