Java Pseudo Transactions With Non-Transactional Resources

When working in enterprise environments, it is often necessary to interact with multiple resources in a single atomic unit of work – a distributed transaction. The ideal way to accomplish this is by leveraging JTA to manage the distributed transaction. However, in transaction scenarios where one or more resources do not support XA transactions JTA cannot be used. This paper describes a method of interacting with non-transactional resources in a pseudo-transaction.

Introduction

When working in enterprise environments, it is often necessary to interact with multiple resources in a single atomic unit of work – a distributed transaction. The ideal way to accomplish this is by leveraging JTA to manage the distributed transaction. However, in transaction scenarios where one or more resources do not support XA transactions JTA cannot be used. This paper describes a method of interacting with non-transactional resources in a pseudo-transaction.

When scenarios that require interaction with non-transactional resources arise, there are several ways to deal with them.

  1. Interaction without regard to transactionality. This approach is appropriate when there is no need for transaction management. Generally, this is when interactions with the resources are read only, or effect non-critical data. This approach is the most common as it requires no extra implementation beyond that of the normal interactions with the target resources; however, it provides no support for data rollback in the event of error conditions, and may result in data synchronization issues.

  2. Development of transactional interfaces. This approach involves making the non-transactional resource transactional by writing new JTA compliant interfaces (implementation of javax.transaction.xa.XAResource) for the non-transactional resources. This approach is the most comprehensive, but also the most time consuming. It is appropriate when data is being manipulated in the resource and business rules dictate that the data is critical and must be kept in sync with data in other systems and when time and technical limitations allow. Details on this approach are beyond the scope of this paper.

  3. Implement the interactions in a “pseudo-transaction” that explicitly checks for and manages error conditions and rollback. This approach is appropriate when when business rules dictate the need for keeping data in target resources synchronized, one or more target resources are non-transactional and implementation of JTA compliant interfaces for these resources is not feasible.

Pseudo-transaction with a single non-transactional resource

Implementation of a pseudo-transaction with a single write or update interaction with a non-transactional resource and one or more transactional resources is straightforward [Read interactions with non-transactional resources have no impact on the pseudo transaction as no data on the target resource is effected]. To implement the pseudo-transaction: First, demarcate the transaction as you normally would in your environment, via deployment descriptor, configuration file, annotations, etc. Implement any interactions with transactional resources as you would normally. As the last piece of logic in the pseudo-transaction, implement the interaction with the non-transactional resource. If an exception occurred during interaction with the non-transactional resource interaction, roll back the managed transaction. If an exception did not occur, commit the managed transaction. We put the interaction with the transactional resources first since we can roll them back if exception occurs on interaction with the non-transactional resource. By putting the interaction with the non-transactional resource as the final action in the transaction, we limit the chance of error occurring after interaction with the non-transactional resource. Table 1 illustrates a simple example of this approach.

Read interactions with non-transactional resources have no impact on the pseudo transaction as no data on the target resource is effected.

    public void brokerDeal(String purchaserAccountId, String sellerAccountId,
                float cost, String widgetId) {

        //acquire handles to the bank account and widget ownership services
        BankAccountService bankService = getBankAccountService();
        WidgetOwnershipService widgetOwnershipService = getWidgetOwnershipService();

        //the interface to bank account is XA compliant; interact with it first

        //transfer funds from one account to the other
        bankService.debitAccount(purchaserAccountId, cost);
        bankService.creditAccount(sellerAccountId, cost);

        //the interface to the WidgetOwnership resource is NOT XA compliant; interact with
        //it last
        try {
            widgetOwnershipService.setOwner(widgetId, purchaserAccountId);
        }
        catch (Throwable t) {
            //Throw a runtime exception and allow the container to roll the
            // transaction back (CMT EJB or Spring)
            throw new RuntimeException("Exception trying to transfer ownership of " +
                    "widget (" + widgetId + ") to (" + purchaserAccountId + ")", t);
        }

    }

Table 1: Example of a pseudo-transaction with a single non-transactional resource

There is really very little to this example. It's as simple as this: in a pseudo-transaction that has any number of interactions with transactional resources and a single interaction with a non-transactional resource, make the non-transactional interaction the final activity in the pseudo-transaction.

Pseudo-transaction with multiple non-transactional resources

In the previous section, we saw how to implement a pseudo transaction with a single non-transactional resource. Unfortunately, this approach is not effective in scenarios where we have multiple interactions with non-transactional resources. This can be a single interaction with each of several non-transactional resources, or multiple interactions with a single non-transactional resource. The approach discussed in the last section fails because as soon as the first interaction with a non-transactional resource has been completed, there is nothing in place to roll that interaction back if anything following fails. We can modify the approach that we took in the last section to work with multiple non-transactional interactions by leveraging the Command pattern [Erich Gamma, Richard Helm, Ralph Johnson, John M. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Reading, MA: Addison-Wesley, 1995) 233-242]. To achieve a pseudo transaction, we encapsulate each non-transactional interaction into a Command object. In each Command object, we implement a rollback method, which is called if a rollback of the pseudo-transaction occurs sometime after the Command has been executed. Table 2 shows a simple implementation of this approach.

    public void brokerDeal(String purchaserAccountId, String sellerAccountId,
                 float cost, String[] widgetIds) {

        //grab handles to the bank account and widget ownership services
        BankAccountService bankService = getBankAccountService();
        WidgetOwnershipService widgetOwnershipService = getWidgetOwnershipService();

        //our interface to bank account is XA compliant; interact with it first since it can
        //be rolled back by the container.

        //transfer funds from one account to the other
        bankService.debitAccount(purchaserAccountId, cost);
        bankService.creditAccount(sellerAccountId, cost);

        //our interface to the WidgetOwnership resource is NOT XA compliant; interact with
        //it last
        ArrayList<WidgetOwnershipServiceTxCommand> WidgetOwnershipSvcCommands =
                new ArrayList<WidgetOwnershipServiceTxCommand>();
        try {
            for (int i = 0; i < widgetIds.length; i++) {
                WidgetOwnershipServiceTxCommand command =
                        new WidgetOwnershipServiceTxCommand(widgetOwnershipService, widgetIds[i],
                        purchaserAccountId);
                WidgetOwnershipSvcCommands.add(command);
                command.transferToNewOwner();
            }
        }
        catch (Throwable t) {
            //Roll back any WidgetOwnershipSvcCommands that have been executed
            for(WidgetOwnershipServiceTxCommand executedCommand:WidgetOwnershipSvcCommands) {
                try {
                    executedCommand.rollback();
                }
                catch (Throwable rollbackT) {
                    //rollback failed; log it.  In production environments,
                    // we would likely send out notifications, write this to
                    // a manual interention queue, etc.
                    log.fatal("Error trying to roll back " +
                            "WidgetOwnershipServiceTxCommand (" + executedCommand + ")",
                            rollbackT);
                }
            }
            //Throw a runtime exception and allow the container to roll the
            // transaction back (CMT EJB or Spring)
            throw new RuntimeException("Exception trying to transfer ownership of " +
                    "widgets to (" + purchaserAccountId + ")", t);
        }

    }

    protected class WidgetOwnershipServiceTxCommand {
        private String widgetId;
        private WidgetOwnershipService widgetOwnershipService;
        private String newOwnerAccountId;
        private String oldOwnerAccountId;

        protected WidgetOwnershipServiceTxCommand(WidgetOwnershipService widgetOwnershipService,
                  String widgetId,
                  String newOwnerAccountId) {

            this.widgetOwnershipService = widgetOwnershipService;
            this.widgetId = widgetId;
            this.newOwnerAccountId = newOwnerAccountId;
        }

        public void transferToNewOwner() {
            //save the existing owner ID in case we need to roll back
            oldOwnerAccountId = widgetOwnershipService.getOwner(widgetId);
            widgetOwnershipService.setOwner(widgetId, newOwnerAccountId);
        }

        public void rollback() {
            widgetOwnershipService.setOwner(widgetId, oldOwnerAccountId);
        }
    }

There are a couple of things to note about this example. Notice that we continue to interact with transactional resources first - since true rollback of these resources can be handled by the container we don't need to add any additional rollback functionality for them. By interacting with the non-transactional resources last, we limit the likelihood of having to manually roll back the non-transactional resource. The less we do after interaction with a non-transactional resource, the less likely it is we'll have to roll it back. Each interaction with the non-transactional resource WidgetOwnershipService is encapsulated in a Command object (WidgetOwnershipServiceTxCommand). As each command is executed, it is added to a List of executed commands. On rollback, the code iterates through the list of executed commands and calls rollback on each. This attempts to undo each command that was executed. It is possible that the rollback operation on the non-transactional resouce will fail; it is important that we log this failure as it means that we now have a potential data synchronization issue and manual intervention is necessary. This is not a true XA rollback, and will not cover many situations that a true XA compliant resource will, but it does cover a large portion of likely error scenarios. Some scenarios that a true XA resource will handle that this approach will not include heuristic exceptions and rollback on container error after completion of the pseudo-transaction method.

Conclusion

When dealing with resources that do not provide a JTA compliant transactional interface, oftentimes there is a need to interface with these resources in a transactional manner. When it is not reasonable to implement JTA compliant interfaces for these resources, a reasonable alternative is to interact with them in a pseudo-transaction. Although this is not as robust and comprehensive as a truly transactional interface, it can provide excellent coverage at a fraction of the development cost of a JTA compliant interface.

Dig Deeper on Software development best practices and processes