How do I safely generate a monotonically increasing sequence for use in e.g. generation a job number?

I have a use case where I need to generate a Job Number associated with many transactions in my app. How do I go about generating this Job Number that will not conflict with other users generating transactions in my app?

The best way to do this in JourneyApps without conflict is to use a CloudCode task that is limited to a single instance. This will ensure that the task never runs in multiple threads and only updates / increments the master job number.

You will notice from the photo that this task also has Webhooks enabled. This allows us to specify the model, e.g.: job that needs to trigger this task.

<model name="job" label="Job">
        <field name="number" label="Number" type="text:number" />
        <field name="timestamp" label="Timestamp" type="datetime" />

        <webhook type="ready" receiver="cloudcode" action="create_job_number" />

        <display>{number}</display> 
    </model>

Also, we will need a model with exactly 1 object in the DB to store our high water mark, rather than querying all jobs to find the next number:

 <model name="job_number" label="SYSTEM: Job Number High Water Mark">
        <field name="high_water_mark" label="High Water Mark" type="number" />
        <display>{high_water_mark:.0f}</display>
    </model>

And finally, the CloudCode code which will execute the task that increments the job number.

export async function run(params) {
    // get the job object that triggered the webhook from the event
    let job = await params.object
    let highWater = await DB.job_number.first();
    
    // create the highWater object if it doesn't exist
    if (!highWater) {
        highWater = DB.job_number.create();
        highWater.high_water_mark = 0;
    }

    console.log(`High water mark ${highWater.high_water_mark}`)
    highWater.high_water_mark ++;
    console.log(`New high water mark ${highWater.high_water_mark}`);
    job.job_number =  highWater.high_water_mark;
    await highWater.save();
    await job.save();
}
5 Likes

This is a very innovative answer, well done. +1

I want to display the sequential number on my view after the cloudcode task is done, but I don’t know the status of the Cloudcode task because it is asynchronous. How can I check the status of the cloudcode task? Or do you have any suggestion for my problem?

You can do this fairly easily by firstly adding a ‘status’ field to the object you are adding the sequential number to and then updating the status to “Processed” or something like that once the number is generated. But you could also just check for the presence of the number, as the sequential number existing on the object implies the task has completed.

That said, one great trick to get the update back into the app in a timely manner is to create and send a Push Notification (PN) to the user that “requested” the new sequential number once the number is assigned. When the user receives this PN it will trigger a sync and thereby basically ensuring that the sequence number is available for use in the app.

So using @jason’s code above I would just add the following

export async function run(params) {
    // get the object from the webhook event
    let job = await params.object

    // get the object tracking the high water mark
    let highWater = await DB.job_number.first();

    // create the tracking object if it doesn't exist
    if (!highWater) {
        highWater = DB.job_number.create();
        highWater.high_water_mark = 0;
    }

    console.log(`High water mark ${highWater.high_water_mark}`)
   
    // increment the high water mark
    highWater.high_water_mark ++;
    console.log(`New high water mark ${highWater.high_water_mark}`);

    // assign the new high water mark to the object 
    job.job_number =   highWater.high_water_mark;
    await highWater.save();
    await job.save();

    // Notify user that sequential number has been assigned
    let pn = DB.push_notification.create();
    pn.message = `${_object.job_number}: Job number assigned`;
    pn.user_id = job.user_id;
    pn.created_at = new Date();

    console.log("Notifying user");
    await pn.save();
}

And the datamodel would need to be updated to include a reference to the user object

<model name="job" label="Job">
    <field name="number" label="Number" type="text:number" />
    <field name="timestamp" label="Timestamp" type="datetime" />

    <belongs-to model="user" name="user" />
    <webhook type="ready" receiver="cloudcode" action="create_job_number" />
    <display>{number}</display> 
</model>