-
Notifications
You must be signed in to change notification settings - Fork 0
Transaction Service
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.
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.
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.
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.
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.
-
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.
-
serveTransaction is invoked, and the TransactionPacket is served based on the following rules:
- closeTransaction is invoked, and the TransactionPacket is removed.
- 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.
- Object opens a transaction
- 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 joins an existing transaction.
- Owned Transactions
- 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 describe overall state, variant data, and Transaction Participant processing state.
- 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.
-
- Not participating, or transaction ended.
-
- Participating.
-
- Participated and completed with a positive response.
-
- serve_type - The service behavior to follow when the packet is being served (via serveTransaction).
-
- 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.
-
- 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.
-
- 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.
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)
- data
In the Transactional Application Components example, this model is referenced to retrieve a value from the object that served the transaction.
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.
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.
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"> </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.
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.
/// 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
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.
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);