This week, a startup doing some vehicle tracking asked me if Zenaton could be used to send an ETA (estimated time of arrival) alert. The idea is to send an alert (text, email, push notification, etc) one hour before a vehicle arrives at its destination.
So here is an example of how we could build a workflow using Zenaton and accomplish this feature without using any additional infrastructure — just write it into our code let the Zenaton workflow engine compute the logic and execute the tasks on our workers.
Calculate ETA and send a notification to the User
The first implementation is really easy:
const BEFORE = 3600;
module.exports = {
*handle(tripId, user) {
let duration;
let eta;
// Calculate the initial duration in seconds and estimated time of arrival (ETA)
[duration, eta] = yield this.run.task("CalculateTimeToArrivalTask", tripId);
// Wait until ETA - 1 hour
yield this.wait.for((duration - BEFORE));
// Send a notification to the customer with updated ETA
yield this.run.task("InformUserOfEtaTask", user, eta);
}
}
This workflow is easily readable:
- first we calculate the current ETA (the
CalculateTimeToArrivalTask
returns an array[duration, eta]
. Remember: in order to ensure idempotency, we cannot use current time in a workflow implementation); - then, we wait up to ETA minus 1 hour (3600 seconds);
- then, we notify the user (the
InformUserOfEtaTask
is where we code the sending of the ETA notification to the user).
To launch such a workflow — eg., when the vehicle starts its trip —we add the following or we could dispatch the workflow from within any application using the Zenaton API.
zenaton.run.workflow("NotifyEtaWorkflow", (tripId, user));
Improved implementation: Update ETA based on traffic conditions
Of course, in real life the ETA will change over time due to traffic conditions or driver behavior. A routing engine will regularly update this ETA and we need to take this into account for an optimal user experience.
So we can add a while
loop the will recalculate the ETA until duration
is close enough to 1 hour. A very simple algorithm is used to avoid putting too much stress on the routing engine used in CalculateTimeToArrivalTask
(note: it would have been really complicated to do the same using crons).
const BEFORE = 3600;
const PRECISION = 120;
module.exports = {
*handle(tripId, user) {
let duration;
let eta;
// Calculate the initial duration in seconds and estimated time of arrival (ETA)
[duration, eta] = yield this.run.task("CalculateTimeToArrivalTask", tripId);
// As long as the remaining duration is more than 1 hour and 2 minutes
while (duration > BEFORE + PRECISION) {
// Wait for half of the time of duration minus one hour
yield this.wait.for((duration - BEFORE) / 2);
// Re-calculate both the duration and the ETA.
[duration, eta] = yield this.run.task("CalculateTimeToArrivalTask", tripId);
}
// Send a notification to the customer with updated ETA
yield this.run.task("InformUserOfEtaTask", user, eta);
}
}
Final implementation: Notify the user if ETA changes by more than 20 minutes
The previous implementation works, but once we’ve put this into production, we realise that we can have modified traffic conditions AFTER having sent the first notification. As such, we may want to also warn the user if the ETA changes by a large amount (eg., more than plus or minus 20 min.) after they receive the first notification.
So, After the first notification, we recalculate ETA every 20 minutes, to check if the ETA is updated to be more than 20 min from the previous ETA notification, then we send an “updated ETA” notification.
This workflow ends when duration
is close enough to 0.
See how we have coded this below:
const BEFORE = 3600;
const PRECISION = 120;
const UPDATE = 1200;
module.exports = {
*handle(tripId, user) {
let duration;
let eta;
let eta2;
// Calculate the initial duration in seconds and estimated time of arrival (ETA)
[duration, eta] = yield this.run.task("CalculateTimeToArrivalTask", tripId);
// As long as the remaining duration is more than 1 hour and 2 minutes
while (duration > BEFORE + PRECISION) {
// Wait for half of the time of duration minus one hour
yield this.wait.for((duration - BEFORE) / 2);
// Re-calculate both the duration and the ETA.
[duration, eta] = yield this.run.task("CalculateTimeToArrivalTask", tripId);
}
// Send a notification to the customer with updated ETA
yield this.run.task("InformUserOfEtaTask", user, eta);
// While it remains more than 20 minutes
while (duration > PRECISION) {
// Wait for 20 minutes
yield this.wait.for(UPDATE);
// Re-calculate both the duration and the ETA
[duration, eta2] = yield this.run.task("CalculateTimeToArrivalTask", tripId);
// If the new ETA has changed more than 20 minutes
const diff = Math.abs(eta2 - eta)
if (diff >= UPDATE) {
// Send a notification to the customer with updated ETA
eta = eta2;
yield this.run.task("NotifyUserOfUpdatedEtaTask", user, eta);
}
}
}
}
So just to recap, with about 20 lines of PHP code, Zenaton can orchestrate the process of sending a notification 1 hour before arrival (taking traffic conditions into account) AND a new notification each time the ETA changes by more than 20 minutes relative to the previous notification.
Conclusion
This example illustrates just how easy Zenaton is to use to implement powerful and readable workflows without having to deal with states or cron, without any database modification or request.
One of the most powerful aspects of Zenaton is demonstrated here — you can really fine-tune and improve your business workflows, literally in just minutes.
Zenaton (https://zenaton.com) is for technical teams that understand that their primary mission is to improve the business through quick iterations and new ideas, not spending most of their time solving purely technical issues. If you have additional questions, feel free to contact me at gilles at zenaton.com or to ask them below 👇