Note
The feature is introduced in 7.9. with a feature flag. Per default the new order creation handler chains are used. To use the old pipeline-based approach, add the following line to your appserver.properties or development.properties (or some other *.properties file that is loaded).
intershop.order.creationchain.enable = false
This glossary describes the terms used in this document:
Term | Description |
---|---|
Handler | The implementation of a small functional unit intended to process a single topic which cannot be broken into smaller parts |
Handler Definition | The integration of a handler into the context of a chain (using the Component Framework) |
Chain | A Chain is composed of one or more Handler Definitions to cover logical steps. From the business point of view it is seen as a sub-chain. |
Chain Definition | A complete business flow from business perspective. It comprises one or more sub-chains to one complete "chain". |
Handlers without any dependence to other handlers should be freely combinable related to their execution order in the handler chain:
Example: It might be wise to process fast validation handlers before time-consuming ones in the pre-order creation handler chain.
However, this can be a bit tricky, because it depends on the particular situation, e.g., checking max item values of three line items might be faster then payment validation checks, but max item check for 1k line items may change conditions completely.
The ChainRegistry
contains all ChainDefinition
(s) registered for a specific application. A ChainDefinition
is added to the ChainRegistry
instance of an application either by the Component Framework, if it is specified in an instances.component file, or by adding an instance of ChainDefinition
programmatically by calling:
com.intershop.component.handlerchain.capi.ChainRegistry::addChainDefinition(ChainDefinition<?, ?> chainDefinition) : void
A ChainDefinition
can be retrieved from the ChainRegistry
by providing its Context
type, Result
type and name:
<C extends Context, R extends Result> com.intershop.component.handlerchain.capi.ChainRegistry::getChainDefinition(Class<C> contextType, Class<R> resultType, String name) : ChainDefinition<C, R>
Example:
... @Inject ChainRegistry chainRegistry; ChainDefinition<ExampleContext, ExampleResult> chainDefinition = chainRegistry.getChainDefinition(ExampleContext.class, ExampleResult.class, "ExampleChain"); ...
Default Implementation: Class com.intershop.component.handlerchain.internal.ChainRegistryImpl
provides a standard implementation of the ChainRegistry
interface.
A ChainDefinition
consists of one or more Chain
(s). If the ChainDefinition
contains multiple Chain
(s) than these ones are linked together and executed in the order they are added to the ChainDefinition
. A Chain
is added to the ChainDefinition
either by the Component Framework, if it is specified in an instances.component file, or by adding an instance of Chain
programmatically by the following method:
com.intershop.component.handlerchain.capi.ChainDefinition::addChain(Chain<C, R> chain) : void
Example:
Following XML content of an instances.component file defines a ChainDefinition
with the name ExampleChainDefinition, consisting of the four Chain
(s) Chain1, Chain2, Chain3, Chain4, that will be instantiated and added to the ExampleChainDefinition by the Component Framework:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <components xmlns="http://www.intershop.de/component/2010"> <fulfill requirement="chainDefinition" of="ChainRegistry"> <instance name="ExampleChainDefinition" with="ChainDefinition" scope="app"> <fulfill requirement="name" value="ExampleChain" /> <fulfill requirement="contextType" value="com.mycompany.component.mycomponent.capi.handlerchain.ExampleContext" /> <fulfill requirement="resultType" value="com.mycompany.component.mycomponent.capi.handlerchain.ExampleResult" /> <fulfill requirement="resultMergeStrategy" with="com.mycompany.component.mycomponent.capi.handlerchain.ExampleMergeStrategy" /> <fulfill requirement="chain"> <instance name="Chain1" with="Chain" scope="app">> <fulfill requirement="name" value="Chain1" /> <fulfill requirement="behaviorOnFailure" value="ROLLBACK" /> <fulfill requirement="transactional" value="false" /> <fulfill requirement="resultFactory" with="com.mycompany.component.mycomponent.capi.handlerchain.ExampleResultFactory" /> </instance> <instance name="Chain2" with="Chain" scope="app">> <fulfill requirement="name" value="Chain2" /> <fulfill requirement="behaviorOnFailure" value="ROLLBACK" /> <fulfill requirement="transactional" value="true" /> <fulfill requirement="resultFactory" with="com.mycompany.component.mycomponent.capi.handlerchain.ExampleResultFactory" /> </instance> <instance name="Chain3" with="Chain" scope="app">> <fulfill requirement="name" value="Chain3" /> <fulfill requirement="behaviorOnFailure" value="ROLLBACK" /> <fulfill requirement="transactional" value="false" /> <fulfill requirement="resultFactory" with="com.mycompany.component.mycomponent.capi.handlerchain.ExampleResultFactory" /> </instance> <instance name="Chain4" with="Chain" scope="app">> <fulfill requirement="name" value="Chain4" /> <fulfill requirement="behaviorOnFailure" value="CONTINUE" /> <fulfill requirement="transactional" value="false" /> <fulfill requirement="resultFactory" with="com.mycompany.component.mycomponent.capi.handlerchain.ExampleResultFactory" /> </instance> </fulfill> </instance> </fulfill> </components>
Default Implementation: Class com.intershop.component.handlerchain.internal.ChainDefinitionImpl
provides a default implementation of the ChainDefinition
interface.
The ChainExecutor
executes a ChainDefinition
, by executing all Chains
of the ChainDefinition
in the order they are added to the ChainDefinition
starting from the first one by calling:
<C extends Context, R extends Result> com.intershop.component.handlerchain.capi.execute(ChainDefinition<C, R> chainDefinition, C context) : R
Execution can be started with a specific Chain
too, by calling:
<C extends Context, R extends Result> com.intershop.component.handlerchain.capi.ChainExecutor::execute(ChainDefinition<C, R> chainDefinition, String subChain, C context) : R
Execution starts at the Chain
with the name provided by the subChain parameter in this case. Execution of previous Chain
(s) of subChain is skipped.
Note
If ChainDefinition::BehaviorOnFailure=ROLLBACK
and the processing of one Handler
of the Chain
specified by subChain finishes with an failure then the method:
<C extends Context, R extends Result> com.intershop.component.handlerchain.capi.Chain::reverseExecute(C context) : R
is called for all preceding Chain
(s) of subChain in the ChainDefinition
, even if these Chain
(s) have not been executed before.
Example: // subChain = chain3
chain3.execute()
chain3.reverseExecute()
chain2.reverseExecute()
chain1.reverseExecute()
A Chain
consists of one or more HandlerDefinition
(s). A HandlerDefinition
is added to a Chain
instance either by the Component Framework, if it is specified in an instances.component file, or by adding an instance of HandlerDefinition
programmatically by the following method:
Chain::addHandler(HandlerDefinition<C> registration) : void
A Chain
can span a single transaction over all their Handler
(s). This means that all Handler
(s) are working in the same transaction.
For the chain framework to be able to manage the transaction on its own, no transaction must be active.
During the execution of the Chain
a Handler
may indicate an error as result of its processing. The value of the BehaviorOnFailure
attribute specified for the Chain
determines how the Chain
reacts in this case.
The Chain
stops further processing. Subsequent Chain
(s) will not be executed.
Handler
(s) of this Chain
are rolled back.Handler
that indicated the failure are rolled backThe Chain
stops further processing. Subsequent Chain
(s) will not be executed. The predecessor Chain
(s) are reverted using the reverse functionality.
Handler
(s) of this Chain
are rolled back.Handler
that indicated the failure are rolled back.Method:
com.intershop.component.handlerchain.capi.Handler<C extends Context>::reverseInvoke(C context) : HandlerResult
is called for all Handler
(s) of the Chain
that have been executed before the one which indicated the failure in reverse order of their previous execution (specified by the position attribute in the HandlerDefinition
). Furthermore this method is called for all Handler
(s) of previous Chain
(s) of the ChainDefinition
.
Note that in case of a chain transaction the Handler
(s) do not necessarily need to rollback their transactional changes by their own in every case. The complete chain transaction will be rolled back. However, this applies only for the Handler(s)
of the Chain
that mark changes to the database. Other changes still need to be rolled back by the handler, e.g., the reset of an inventory via an remote inventory service, since such changes are not covered by the database rollback.
The Chain
continues its processing. Subsequent Chain
(s) will be executed.
Handler
(s) of Chain
are rolled back.Handler
that indicated the failure are rolled back.Default Implementation: Class com.intershop.component.handlerchain.internal.ChainImpl
provides a default implementation of the Chain
interface.
Example:
Following XML content of an instances.component file defines three HandlerDefinition
(s) instantiated and added to the Chain1 by the Component Framework.
<components xmlns="http://www.intershop.de/component/2010"> <fulfill requirement="handler" of="Chain1"> <instance with="HandlerDefinition" name="Handler1"> <fulfill requirement="name" value="Handler1"/> <fulfill requirement="position" value="1"/> <fulfill requirement="handler"> <instance with="com.mycompany.component.mycomponent.capi.handler.Handler1"/> </fulfill> </instance> </fulfill> <fulfill requirement="handler" of="Chain1"> <instance with="HandlerDefinition" name="Handler2"> <fulfill requirement="name" value="Handler2"/> <fulfill requirement="position" value="3"/> <fulfill requirement="handler"> <instance with="com.mycompany.component.mycomponent.capi.handler.Handler2"/> </fulfill> </instance> </fulfill> <fulfill requirement="handler" of="Chain1"> <instance with="HandlerDefinition" name="Handler3"> <fulfill requirement="name" value="Handler3"/> <fulfill requirement="position" value="2"/> <fulfill requirement="handler"> <instance with="com.mycompany.component.mycomponent.capi.handler.Handler3"/> </fulfill> </instance> </fulfill> </components>
A HandlerDefinition
is a simple structure comprising a Handler
implementation, the name of the handler in the Chain
and the position of the handler in the execution order of the Chain
.
Handler
(s) are added to the Chain
by HandlerDefinition
(s) for the following reasons:
Handler
instance can be added to the same Chain
more than once.Chain
cannot be specified at the Handler
instance directly.Beside the ChainDefinition
an instance of the Context
interface has to be provided to the ChainExecutor
by the caller:
public interface ExampleContext extends Context { .... } public class ExampleContextImpl extends ContextImpl implements ExampleContext { .... }
Sub-types of the Context
interface should reflect the specific use-case by defining getter- and setter-methods for the data required by the Handler
(s) of the Chain
(s), e.g., Handler
(s) of a Chain
that creates an order from a basket require at least the basket. ChainExecutor
forwards the Context
to the first Chain
of the ChainDefinition
that is executed as well as whose successors.
The Chain
itself does not provide any functionality. Functionality is provided by the Handler(s) that are added to the chain.
Chain
provides the Context
as parameter of the invoke(Context context)
method to the Handler
(s).Handler
may change the Context by providing or changing any data, e.g. an Order.A Handler
returns its processing state that can be:
SUCCESS
: There were no errors during processing of the handler. Note that there still may be warnings!FAILURE
: Processing of the handler led to a failure. Chain
decides on further processing based on the value specified for BehaviorOnFailure
that can be:STOP
: Further processing of the Chain
and its successors is stopped. Chain
is left.ROLLBACK
: The chain is reversed starting with the predecessor of the failing handler. The Handler which failed has to cover the rollback of the successful completed parts of his own actions by itself.CONTINUE
: Further processing of the Chain
and its successors continues. Handler
(s) that are invoked afterwards may be called with another state than expected. So this behavior is suitable for chains that to not change or create any data.
STOP
: Further processing of the Chain
and its successors is stopped. Chain
is left.Processing state of all Handler
(s) of the Chain
determines the processing state of the Chain
:
SUCCESS
} -> COMPLETED
STOP
}, {SUCCESS
, STOP
} -> STOPPED
FAILURE
}, {SUCCESS
, FAILURE
} -> STOPPED
(BehaviorOnFailure
= STOP
)FAILURE
}, {SUCCESS
, FAILURE
} -> COMPLETED
(BehaviorOnFailure
= CONTINUE
)txChain
- Transaction of the chain.txHandler
- Transaction of the handler.Scenario | Chain Transaction | Handler Transaction | ChainBehaviorOnFailure | Transactional | Chain Behavior | Remark |
---|---|---|---|---|---|---|
1a | Y | (Y/N) | STOP | txChain.rollback | Chain is left. | This scenario is not possible with BehaviorOnError.CONTINUE or BehaviorOnError.ROLLBACK . |
2a | N | Y | STOP | txHandler.rollback | Chain is left. | Invocation of |
2b | N | N | STOP | Chain is left. | ||
3a | Y | N | ROLLBACK | txChain.rollback | reverseInvoke() method of all handlers is called before chain is left. | Transaction is rolled back as well as reverseInvoke() methods of previous handlers are invoked in this case.Note that in the case of a re-entry in the chain the changes made by previous handlers have been already committed. Example: Handlers have already created the order in the order creation chain. |
3b | N | Y | ROLLBACK | txHandler.rollback | reverseInvoke() method of all handlers is called before chain is left. | |
3c | N | N | ROLLBACK | reverseInvoke() method of all handlers is called before chain is left. | Nevertheless is invocation of reverseInvoke() method of previous handlers necessary, because there might a previous handler changed persistent data by its own transaction. | |
4a | N | Y | CONTINUE | txHandler.rollback | invoke() method of next handlers is called. | |
4b | N | N | CONTINUE | invoke() method of next handlers is called. |
For a list of handlers, please have a look at Cookbook - Order Creation | Recipe: List Chains and Handlers.
Name of Chain | Description | Failure Handling | Transactional |
---|---|---|---|
PreOrderCreationChain | This chain is executed before the actual order is created. The purpose of this chain is to check if all preconditions for an actual order are fulfilled. This includes a final basket validation, checks for approval status etc. If all preconditions are fulfilled, the basket is locked to prevent simultaneous checkout of the same basket either via REST or different browsers. The order creation chain might be left in this chain, for example for the approval workflow. | Rollback on Failure | no |
OrderCreationChain | This chain creates the actual order with all needed entities, like product line items, addresses, payments etc. and also creates the document number for the order. All order related data should be written in this chain. Once the order is written to the database at the end of this chain, it should not be changed anymore except for status updates. In case of failures, the chain (and with this the database transaction) is rolled back. The result is the same state as before the order creation was started: with a valid basket and no according order. | Rollback on Failure | yes |
PaymentAuthorizationOrderCreationChain | This chain takes care of necessary payment authorizations that need a redirect after checkout. If a redirect is necessary, the chain is left for that redirect. A callback from a payment authorization will jump back into this chain for further order creation handling or more redirects. If a callback returns with a failure, the order creation is rolled back, which results in a cancelled order. | Rollback on Failure | no |
PendingPaymentOrderCreationChain | A chain for processing all actions that do not need to wait for a successful completion of the payment, like sending of an order confirmation mail. | Continue on Failure | no |
PaymentSynchronizationOrderCreationChain | The PaymentSynchronizationChain waits for all asynchronous payment notifications. Notifications from payments start the order creation in this chain. If all notifications were successful, the order creation proceeds with the post order creation handling. If a notification is received with failures, the order creation is rolled back. | Rollback on Failure | no |
PostPaymentOrderCreationChain | This chains contains all post order handling, like creation of gift certificates, all necessary promotion handling, budget handling etc. Problems and warnings in this chain will not lead to a rollback anymore. | Continue on Failure | no |
Basic handler chain infrastructure and handler implementation classes have to be tested via JUnit mock tests.
Complete order creation flows - means finally configured (wired) handler chains - have to be tested by JUnit embedded server tests.