OOTB hybris allows to automatically retry business process action after some time. To do this is enough to set delay in RetryLaterException
and throw it. But there is no OOTB implementation of limiting amount of such retries in business processes.
Seems like to implement such behavior is enough to:
- Extend
BusinessProcess
with custom integer attribute, which will hold current retry iteration:
1
2
3
4
5
6
7
8
9
10
|
<itemtype code="BusinessProcess" autocreate="false" generate="false">
<attributes>
<attribute qualifier="retry" type="java.lang.Integer">
<defaultvalue>new Integer(0)</defaultvalue>
<persistence type="property"/>
<modifiers optional="false" read="true" write="true"/>
</attribute>
</attributes>
</itemtype>
|
- In business process action populate and save this attribute before throwing retry exception:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class ExampleAction extends AbstractSimpleDecisionAction<OrderProcessModel> {
@Override
public Transition executeAction(final OrderProcessModel process) {
if (process.getRetry() <= MAX_RETRIES) {
process.setRetry(process.getRetry() + 1);
modelService.save(process);
RetryLaterException ex = new RetryLaterException("Retry ");
ex.setDelay(1000);
throw ex;
} else {
throw new IllegalStateException("Fail process.");
}
}
}
|
Unfortunately it would not work and after debugging you could see that retry attribute is always 0 and is not persisted to db. This is OOTB behaviour of process execution in DefaultBusinessProcessService
. All actions are executed within transactions. If action execution throws error, transaction would be reverted and no changes would be persisted to DB. So retry attribute for BusinessProcessModel
will never change its value.
According to DefaultTaskExecutionStrategy#run
coulbe used isRollBack
value of RetryLaterException
to skip exception throwing, so Transaction#finishExecute
will not rollback changes, what will allow to save amount of retries on BusinessProcessModel
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class ExampleAction extends AbstractSimpleDecisionAction<OrderProcessModel> {
@Resource
private ModelService modelService;
@Override
public Transition executeAction(final OrderProcessModel process) {
if (process.getRetry() <= MAX_RETRIES) {
process.setRetry(process.getRetry() + 1);
modelService.save(process);
RetryLaterException ex = new RetryLaterException("Retry ");
ex.setDelay(1000);
ex.setRollBack(false); // Will skip transaction rollback on exception
throw ex;
} else {
throw new IllegalStateException("Fail process.");
}
}
}
|
Keep in mind that you retry method for RetryLaterException
can be set up. By default, is used RetryLaterException.Method.EXPONENTIAL
, which will use 2 to the power of retries with some random deviation. It means that code above would be executed in around 1 sec, 2 sec, 4 sec, 8 sec etc.
To manually control time of retries could be used RetryLaterException.Method.LINEAR
:
1
2
3
4
5
6
7
8
9
|
// Will retry in 10 min, in 20 min, in 30 min etc ..
int delayInMinutes = businessProcessModel.getRetry() * 10;
RetryLaterException ex = new RetryLaterException();
ex.
setDelay(delayInMinutes *60*1000);
ex.
setMethod(RetryLaterException.Method.LINEAR);
|
UPD 2020-09-03
One more way to bypass not saving BusinessProcessModel
on exception throw. For that we should close current transaction, save changes in BusinessProcessModel
separate transaction, and create one more transaction, which would be roll backed by DefaultBusinessProcessService
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public class ExampleAction extends AbstractSimpleDecisionAction<OrderProcessModel> {
@Resource
private ModelService modelService;
@Override
public Transition executeAction(final OrderProcessModel process) {
modelService.refresh(process);
if (process.getRetry() <= MAX_RETRIES) {
// On exception throw in action hybris transaction manager will rollback everything
// But we need to store amount of retries, that`s why transaction is rollbacked here
// and new transaction is started and commited
// Rollback action transaction
Transaction currentTransaction = Transaction.current();
if (currentTransaction.isRunning()) {
currentTransaction.rollback();
}
// Create and commit transaction to persist amount of retries
currentTransaction.begin();
process.setRetry(process.getRetry() + 1);
modelService.save(process);
currentTransaction.commit();
// Create new transaction, which would be rollbacked by task service
currentTransaction.begin();
RetryLaterException ex = new RetryLaterException("Retry ");
ex.setDelay(1000);
throw ex;
} else {
throw new IllegalStateException("Fail process.");
}
}
}
|