Skip to content

Transaction Service

StephenCote edited this page Sep 30, 2013 · 5 revisions

Foreword

Like the Task Service, the Transaction Service is a foundation of the Hemi Framework, and one reason the MDI toolkit was discontinued in favor of the M3 design in 2002 (which became Engine, and now Hemi). While the Task Service underwent several architectural designs, the mechanism by which dissimilar objects exchanged stateful data was extracted as the Transaction Service.

Introduction

Transactions are stateful communication bands shared by dissimilar objects. Objects registered as Transaction Participants share managed state for a named Transaction Packet.

Transactions are served by request to each participant until all participants specify a termination status.

Related Reading

Transaction Service API

Transaction Service Integration

The following Hemi JavaScript Framework services and libraries use the Transaction Service.

  • Task Service - For orchestrating task state.
  • Data IO Service - For orchestrating Data IO Providers
  • Canvas - For coordinating with other Canvas instances.
  • Application Components - Auto-instrument transaction participation across same-type or declared-type transactions.

And, the following services use the Task Service, indirectly using the Transaction Service.

  • Test Module Service - For tracking unique test status across a suite.
  • Application Space Service - For coordinating bootstraps and dependencies.

Transactions

A transaction is comprised of a Transaction Packet and participating Transaction Participants. Any object registered with the Transaction Service may join a transaction by participating with the packet.

Transaction Processing Flow

The Transaction Service operates in a warm standby mode, waiting for an instruction to serve a specified transaction. The service starts automatically when included. The following flow describes the lifecycle of participation and packets.

Packet Flow

  • openTransaction is invoked, and a TransactionPacket is created.
    • serveTransaction is invoked, and the TransactionPacket is served based on the following rules:
      • If serve_type is 1, the TransactionPacket will be served to a participant until the participant returns true.
      • If serve_type is 2, the TransactionPacket is served once to a given participant, and never again, regardless of its return status.
    • endTransaction is invoked, and the TransactionPacket is moved to a finalized state.
  • closeTransaction is invoked, and the TransactionPacket is removed.

Participation Flow

  • Object Registers with Transaction Service
    • Owned Transactions
      • Object opens a transaction
        • A TransactionPacket is created, owned by Object.
        • startTransaction is invoked on Object for new packet.
      • Object configures TransactionPacket as needed.
      • Object, or other process, serves TransactionPacket
        • doTransaction is invoked for TransactionPacket participants if they have not indicated successful processing by returning true.
      • Object, or other process, continues to serve TransactionPacket as needed.
      • Object determines to close transaction, or configures it to close automatically.
        • endTransaction is invoked for all TransactionPacket participants.
    • Joined Transactions
      • Object joins an existing transaction.
        • Object is added to TransactionPacket participants.
        • startTransaction is invoked on object for packet.
      • doTransaction is invoked on object for packet when served.
      • endTranaction is invoked when transaction is closed (all participants may not have successfully processed the transaction).
  • Object Unregisters with Transaction Service
    • All open transactions owned by this object are closed.
    • Object is removed from participating in all other joined transactions.

Transaction Packets

Transaction Packets describe overall state, variant data, and Transaction Participant processing state.

Transaction Packet API

Properties

  • data - placeholder for variant data. Refer to the following section on related Packet Data Models.
  • errors - placeholder for variant data.
  • is_finalized - bit indicating whether the packet has been finalized. Packets are marked as finalized when the transaction is stopped.
  • is_open - bit indicating whether the packet is still open. Packets are marked as being not open when the transaction is stopped. Packets that are not open are not served.
  • owner_id - The framework object id that owns the transaction.
  • packet_id - Unique identifier.
  • packet_name - The name of the packet, effectively the name of the transaction.
  • packet_state - placeholder for variant use.
  • participants - hash of participant object identifiers. The hash value identifies the participation state.
      1. Not participating, or transaction ended.
      1. Participating.
      1. Participated and completed with a positive response.
  • serve_type - The service behavior to follow when the packet is being served (via serveTransaction).
      1. On every call to serveTransaction, serve the packet to any participant that has not indicated it has successfully processed the packet. This is also known as Serve Until True.
      1. On every call to serveTransaction, serve the packet to any participants that have not yet been served, and otherwise do not serve the packet regardless of participant processing state. This is also known as Serve Once.

Methods

  • setBlockEndTransaction - Instruct the transaction to block processing the end transaction state. This affects the invocation of the endTransaction API method. Also, the participant state is not updated, so the packet may be in a finalized and closed state while the participant states remain open.
  • setBlockServeTransaction - Instruct the transaction to block being served. This affects innvocation of the doTransaction API method.
  • setBlockStartTransaction - Instruct the transaction to block being started. This only affects the innvocation of the startTransaction API method, and does not affect the transaction state or prevent it from being served.

Packet Data Model

Transaction Packets define a placeholder for variant data. The joinTransactionPacket method injected by addServiceAPI uses this placeholder to add a second level model:

  • TransactionPacket
    • data
      • data (variant)
      • src (object)
      • type (packettype)

In the Transactional Application Components example, this model is referenced to retrieve a value from the object that served the transaction.

Transaction Participants

Transaction Participants are registered framework objects that implement the required Transaction Participant API. The required API may be manually define, instrumented per object, or provided by a base implementation.

Transactional Components

Application Components are instrumented with the Transaction Service API, and the addServiceAPI method was originally extracted from helper implementations defined in the Application Component library.

Application Components provide the quickest way to work with transactions.

Example Transactional Component

The following example demonstrates how two components may participate in a common transaction.

First, create a component that shares participation for the reflect transactions, and that listens for onkeyup events and serves a transaction packet named keypunch. Assume this component is saved to Hemi/Components/component.reflector.xml.

<application-components>
   <application-component id = "reflector" participant-id = "reflect">
      <![CDATA[
         component_init : function(){
            /// TODO: Fixed in 3.2; add onkey* to the default event capture list for components
            this.scopeHandler("keyup",0,0,1);
            Hemi.event.addEventListener(this.getContainer(),"keyup",this._prehandle_keyup)
         },
         _handle_keyup : function(){
            /// The double .data.data is from using the convenience addServiceAPI version, which automatically creates adds a common object for the packet data property, including its own data property
            this.getPacket("reflect").data.data = this.getContainer().value;
            this.serveTransaction("keypunch",this);
         },
         component_destroy : function(){
            Hemi.event.removeEventListener(this.getContainer(),"keyup",this._prehandle_keyup);
         }
      ]]>
   </application-component>
</application-components>

Second, create a component participates in the same reflect transaction and that responds to a named transaction packet keypunch by changing its contents to the value. This example assumes the serving component transcribed a value to the packet. Assume this component is saved to Hemi/Components/component.reflected.xml

<application-components>
   <application-component id = "reflected" participant-id = "reflect">
      <![CDATA[
         _handle_keypunch : function(s, v){
            Hemi.xml.setInnerXHTML(this.getContainer(),v.data.data);
         }
      ]]>
   </application-component>
</application-components>

Third, defined the two components for an input field and a span element.

<input type = "text" component = "reflector" />
<p>Reflected Value: <span component = "reflected">&nbsp;</span></p>

When a value is typed into the input field, it is then printed in the span tag. In this case, the two components are connected by a named transaction. Furthermore, the components could just as easily branch out to other transactional channels.

Required API

Instrumented API

The Transaction API may be instrumented for an existing object using the addServiceAPI method. The instrumented API injects the required API as needed, and includes additional helper methods. The injected API includes:

  • (internal) doTransaction - Invokes _handle_transaction and _handle_packettype.
  • (internal) startTransaction - Invokes _handle_begin_transaction.
  • (internal) endTransaction - Invokes _handle_end_transaction.
  • getPacket Convenience method for retrieving the Transaction Packet outside of the service calls.
  • _handle_begin_transaction (virtualized by injected startTransaction)
  • _handle_end_transaction - (virtualized by injected endTransaction)
  • _handle_transaction - (virtualized by injected doTransaction)
  • _handle_packettype - (virtualized by the injected doTransaction)
  • joinTransactionPacket - Convenience method for joining a named transaction from the object context.
  • removeFromTransactionPacket - Convenience method for removing an object from a named transaction from the object context.
  • serveTransaction - Convenience method for serving a transaction from the object context.

Instrumented API Example

/// Given some existing non-framework objects
///
var oObject1 = {
   _handle_transaction : function(TransactionService, TransactionPacket){
      /// All participating transaction packets
   }
};

var oObject2 = {
   _handle_transaction : function(TransactionService, TransactionPacket){
      /// All participating transaction packets
   },
   _handle_testpacket1 : function(TransactionService, TransactionPacket){
      /// Only the named participating transaction packet
}
};

/// Prepare for the framework
Hemi.prepareObject("type1","1.0",true, oObject1, true);
Hemi.prepareObject("type2","1.0",true, oObject2, true);

/// Register objects and instrument the service API
Hemi.tranaction.service.register(oObject1, true);
Hemi.tranaction.service.register(oObject2, true);

/// Join the objects to a transaction
oObject1.joinTransactionPacket("demo");
oObject2.joinTransactionPacket("demo");

/// Serve the transaction from Object 1, and note to serve to itself
oObject1.serveTransaction("testpacket1", "source", 1);

/// Object2 will receive invocations to both _handle_transaction and it's _handle_packettype, _handle_testpacket1

Manual API Declaration

Manually specifying the transaction API eschews the convenience methods of the injected API for greater control over packet data, processing, and allowing for multiple transaction service scopes. It also requires manually configuring the packet and joining participants to the packet.

Manual API Example

var oObject1 = {
   startTransaction : function(TransactionService, TransactionPacket){
  
   },
   endTransaction : function(TransactionService, TransactionPacket){
  
   },
   doTransaction : function(TransactionService, TransactionPacket){
      var bKeepServing = true;
      return bKeepServing;
   }
};
var oObject2 = {
   startTransaction : function(TransactionService, TransactionPacket){
  
   },
   endTransaction : function(TransactionService, TransactionPacket){
  
   },
   doTransaction : function(TransactionService, TransactionPacket){
      var bKeepServing = true;
      return bKeepServing;
   }
};

/// Prepare for the framework
Hemi.prepareObject("type1","1.0",true, oObject1, true);
Hemi.prepareObject("type2","1.0",true, oObject2, true);

/// Register objects and instrument the service API
Hemi.tranaction.service.register(oObject1, true);
Hemi.tranaction.service.register(oObject2, true);

/// Mockup some packet data
///
var oData = {
   id:"123",
   name:"Demo"
};

/// Open the transaction, and assign oObject1 as the owner
var sTransId = Hemi.transaction.service.openTransaction(
   "testpacket1", oObject1, oData
);

/// Get the packet
var oPacket = Hemi.transaction.service.getPacketByName("testpacket1");
/// Instruct oObject2 that the transaction is starting (startTransaction callback)
oPacket.setBlockStartTransaction(false);

/// Add oObject2 to the transaction
Hemi.transaction.service.addTransactionParticipant(oObject2, oPacket);

/// Serve the transaction to the owner and all participants
Hemi.transaction.service.serveTransaction(oObject1, oObject1.getObjectId(), false);

Clone this wiki locally