Journey Manager (JM) The transaction engine for the platform. | Form Builder Platform Developer | All versions This feature is related to all versions.
Manager already provides the capacity for multi-step delivery processes through the use of delivery checkpoints in the Groovy Delivery Process. However, for larger or more complicated delivery methods, it may be easier to separate delivery steps into individual delivery processes. This has the added advantage of allowing forms to call only the delivery method they need. For forms that require these multi-step delivery methods, it is possible to configure a Groovy delivery process to call other delivery processes. This allows form developers to create a type of landing point for form submissions that can branch off to any number of different delivery methods, providing a wide range of functionality for accepting form submissions.
First, lets make a basic Groovy Delivery Process. Log in to Manager, go to the service definitions page and click new and fill out the new service information.
The basic groovy delivery process will have the following code provided for you:
if (deliveryCheckpoint.doCheckpoint("Checkpoint A", "Description...")) {
try {
// TODO: provide your custom delivery code
deliveryCheckpoint.compietedCheckpoint("Checkpoint A")
} catch (Throwable e) {
deliveryCheckpoint.errorCheckpoint("Checkpoint A", e)
}
}
if (deliveryCheckpoint.doCheckpoint("Checkpoint B", "Description...")) {
try {
// TODO: provide your custom delivery code
deliveryCheckpoint.compietedCheckpoint("Checkpoint B")
} catch (Throwable e) {
deliveryCheckpoint.errorCheckpoint("Checkpoint B”, e)
}
}
When this process is called, after a form submission is made, it will first run the code in the try/catch block labled Checkpoint A and then the code in the next try/catch block labeled Checkpoint B. If the code in the try catch block should fail it will retry this code again in 15 mins.
The delivery retry delay and the number of retry attempts can be adjusted in the service parameters of the delivery process, the fields are retryDelayMins and maxDeliveryAttempts.
In this code even if Checkpoint A should fail Checkpoint B will still make an attempt to be delivered, this is known as a parrallel delivery method. You can also setup the delivery process to be serial, this is useful if you have delivery processes that rely on each other. The groovy code for a serial delivery looks like this:
if (deliveryCheckpoint.doCheckpoint("Checkpoint A", "Description...")) {
try {
// TODO: provide your custom delivery code
deliveryCheckpoint.compietedCheckpoint("Checkpoint A")
} catch (Throwable e) {
deliveryCheckpoint.errorCheckpoint("Checkpoint A", e)
}
}
if (deliveryCheckpoint.isCompleted("Checkpoint A") &&
deliveryCheckpoint.doCheckpoint("Checkpoint B", "Description...")) {
try {
// TODO: provide your custom delivery code
deliveryCheckpoint.compietedCheckpoint("Checkpoint B")
} catch (Throwable e) {
deliveryCheckpoint.errorCheckpoint("Checkpoint B”, e)
}
}
This code won't call the second checkpoint until the first has been successfully completed. For more information, see Delivery Function.
It is possible,using groovy script, to call another process from within your delivery process, as shown below:
IDeliveryProcessService deliveryService = (IDeliveryProcessService) ServiceLocator.getServiceForName('[SERVICE_NAME]'))
deliveryService.deliverSubmission(submission, new DeliveryDetails())
These two lines of code first locate the service by its name, then calls the service by passing the submission object and delivery details to the service. Using this code in a groovy delivery service it is possible to create a landing point for submissions that can be delivered in a variety of methods based on any number of factors, such as submission data or other successful delivery methods.
Let's look at an example below.
A form is submitted that calls a basic delivery process 'logger', which logs the form submission data for review by a backend system. The form is updated to include an email delivery function only if a certain email is input into the form. To get this functionality to work a delivery process is created to make the initial 'logger' delivery and also check for the email and complete that delivery process if it is found. The snippet of the code is shown below.
Dual Delivery will look like this:
import com.avoka.fc.core.entity.DeliveryDetails
import com.avoka.fc.core.entity.Submission
import com.avoka.fc.core.dao.SubmissionDataDao
import com.avoka.fc.core.service.IDeliveryProcessService
import com.avoka.fc.core.service.ServiceLocator
import com.avoka.fc.core.service.submission.DeliveryCheckpointService
import com.avoka.fc.forms.api.DeliveryException
import java.util.List
if (deliveryCheckpoint.doCheckpoint("Checkpoint A", "Description...")) {
try {
// Call first delivery process
IDeliveryProcessService deliveryService = (IDeliveryProcessService) ServiceLocator.getServiceForName('Logger Delivery')
deliveryService.deliverSubmission(submission, new DeliveryDetails())
deliveryCheckpoint.completedCheckpoint("Checkpoint A")
} catch (Throwable e) {
deliveryCheckpoint.errorCheckpoint("Checkpoint A", e)
}
}
if (deliveryCheckpoint.doCheckpoint("Checkpoint B", "Description...")) {
try {
def list = submission.getSubmissionExtractData();
//Iterate through Submission Data
for(def temp in list){
//Check for Email Field with specifc Email value
if(temp.getName() == "email" && temp.getValue() == "[email protected]"){
//Call Second Delivery process
IDeliveryProcessService deliveryService = (IDeliveryProcessService) ServiceLocator.getServiceForName('Email Delivery')
deliveryService.deliverSubmission(submission, new DeliveryDetails())
}
}
deliveryCheckpoint.completedCheckpoint("Checkpoint B"
} catch (Throwable e) {
deliveryCheckpoint.errorCheckpoint("Checkpoint B", e)
}
}
Delivering transactions is hard work, and often involves more than one step. Let’s say our delivery job entails zipping up the transaction’s bits (form receipts, XML data, attachments, and whatnot) into a zip file then sending that file to the bank by secure FTP. So that’s two things: the zipping and the sending. Let’s worry about what happens when the zipping goes well, but the sending not so much. Delivery processes are clever enough to retry themselves when they fail, but we don’t want to do the zipping all over again (assuming we’ve saved the zipped file somewhere from the previous attempt). We just want to retry the sending bit. To handle that, we can use checkpoints.
Below is the code from a delivery service, which uses checkpoints. Instead of actually zipping and sending, I have decided to just flip a coin at each step to determine whether to fail or succeed. This allowed me to explore what happens with my transaction and my job when the delivery retries.
{
import com.avoka.tm.svc.*
import com.avoka.tm.util.*
import com.avoka.tm.vo.*
import groovy.transform.TypeChecked
@TypeChecked
class FluentDeliveryProcess {
// Injected at runtime
public Logger logger
DeliveryResult invoke(SvcDef svcDef, Txn txn) {
TxnCheckpointSvc checkpointSvc = new TxnCheckpointSvc(txn)
Random random = new Random()
Integer maxDeliveryAttempts = txn.getDeliveryMaxAttempts() ?: 10
Integer previousDeliveryAttempts = txn.getDeliveryProcessAttempts() ?: 0
Integer currentDeliveryAttempt = previousDeliveryAttempts + 1
logger.info("this is attempt $currentDeliveryAttempt of $maxDeliveryAttempts")
Integer attemptsRemaining = maxDeliveryAttempts - currentDeliveryAttempt
boolean isLastAttempt = attemptsRemaining < 1
if (checkpointSvc.doCheckpoint("Checkpoint A")) {
try {
logger.info("Trying the code before checkpoint A")
def nextBoolean = random.nextBoolean()
logger.info("nextBoolean: " + nextBoolean)
if (nextBoolean) { logger.info("completing checkpoint A")
checkpointSvc.complete("Checkpoint A")
}
else {
logger.info("Error before completing checkpoint A")
throw new RuntimeException("Error before completing checkpoint A")
}
} catch (Throwable e) {
checkpointSvc.error("Checkpoint A", e.toString())
return new DeliveryResultBuilder()
.setStatus(isLastAttempt ? Txn.DELIVERY_UNDELIVERABLE : Txn.DELIVERY_ERROR)
.build()
}
}
if (checkpointSvc.isCompleted("Checkpoint A") && checkpointSvc.doCheckpoint("Checkpoint B")) {
try {
logger.info("Trying the code before checkpoint B")
def nextBoolean = random.nextBoolean()
logger.info("nextBoolean: " + nextBoolean)
if (nextBoolean) {
logger.info("completing checkpoint B")
checkpointSvc.complete("Checkpoint B")
}
else {
logger.info("Error before completing checkpoint B")
throw new RuntimeException("Error before completing checkpoint B")
}
} catch (Throwable e) {
checkpointSvc.error("Checkpoint B", e.toString())
return new DeliveryResultBuilder()
.setStatus(isLastAttempt ? Txn.DELIVERY_UNDELIVERABLE : Txn.DELIVERY_ERROR)
.build()
}
}
return new DeliveryResultBuilder()
.setStatus(Txn.DELIVERY_COMPLETED)
.build()
}
}
You can see that delivery service tracks when we’ve exhausted the configured number of retries and quits delivering by setting the delivery status to UNDELIVERABLE. Manager has a built in user group called Receive System Alerts – Delivery. When a transaction fails to deliver and there are no remaining attempts, Journey Manager sends an email alert to all members of this group for the organization. In this way, the attention of a system administrator can be brought to the problem.
In some cases, the administrator can fix the problem and retry delivery. For more information, see Receive Delivery Escalation Alerts.
Next, learn how to view Transact functions.