๐Ÿ”ท Core

cbq

Use this skill when implementing asynchronous background job processing in ColdBox/BoxLang with cbq. Covers job class creation, queue configuration (database, Redis, SQS), dispatching jobs, delayed dispatch, queue routing, retry handling, worker pool management, and monitoring patterns.

$ npx skills add coldbox/skills/modules/cbq
$ coldbox ai skills install coldbox/skills/modules/cbq
๐Ÿ”— https://skills.boxlang.io/skills/raw/coldbox/skills/modules~cbq

CBQ Skill

When to Use This Skill

Load this skill when:

  • Offloading slow tasks (email, PDF, API calls) to background workers
  • Implementing retry logic for unreliable external integrations
  • Routing different job types to different queue backends
  • Scheduling delayed job execution
  • Building worker consumers for database or Redis queues

Installation

box install cbq

Configuration

config/modules/cbq.cfc

function configure() {
    return {
        // Default queue connection
        defaultConnection : "database",

        connections : {
            database : {
                driver    : "CBQDatabase",
                dsn       : "myDatasource",
                tableName : "cbq_jobs",
                queue     : "default",
                timeout   : 60
            },
            redis : {
                driver : "CBQRedis",
                host   : getSystemSetting( "REDIS_HOST", "127.0.0.1" ),
                port   : getSystemSetting( "REDIS_PORT", "6379" ),
                queue  : "default",
                timeout: 60
            }
        },

        // Worker configuration
        workers : {
            numThreads  : 5,
            sleep       : 1,    // seconds between polls
            maxAttempts : 3     // global default
        }
    }
}

Create Database Table (cfmigrations)

function up( schema ) {
    schema.create( "cbq_jobs", ( table ) => {
        table.increments( "id" )
        table.string( "queue" ).default( "default" )
        table.string( "connection" ).default( "database" )
        table.longText( "payload" )
        table.unsignedTinyInteger( "attempts" ).default( 0 )
        table.boolean( "reserved" ).default( false )
        table.dateTime( "reserved_at" ).nullable()
        table.dateTime( "available_at" )
        table.dateTime( "created_at" )
    } )

    schema.create( "cbq_failed_jobs", ( table ) => {
        table.increments( "id" )
        table.string( "connection" )
        table.string( "queue" )
        table.longText( "payload" )
        table.longText( "exception" )
        table.dateTime( "failed_at" )
    } )
}

Creating Jobs

// jobs/SendWelcomeEmailJob.bx
class extends="cbq.models.AbstractJob" {

    // Inject services the job needs
    property name="mailService" inject="MailService@myApp";

    // Job payload properties
    property name="userId"  type="numeric";
    property name="email"   type="string";

    // Optional โ€” override defaults from config
    this.queue       = "emails"
    this.connection  = "default"
    this.maxAttempts = 3
    this.timeout     = 30

    function handle() {
        var user = userService.get( userId )

        mailService
            .newMail(
                to      = email,
                subject = "Welcome to the platform!",
                type    = "html"
            )
            .setBodyTemplate( "email/templates/welcome" )
            .setBodyTokens( { name: user.getName() } )
            .send()
    }

    // Called when all retries exhausted
    function failed( exception ) {
        log.error( "Failed to send welcome email to #email# after #this.maxAttempts# attempts: #exception.message#" )
    }
}

Dispatching Jobs

property name="sendWelcomeEmailJob" inject="SendWelcomeEmailJob";

// Dispatch immediately
sendWelcomeEmailJob
    .setUserId( newUser.getId() )
    .setEmail(  newUser.getEmail() )
    .dispatch()

// Delay execution (seconds)
sendWelcomeEmailJob
    .setUserId( rc.id )
    .setEmail(  rc.email )
    .delay( 30 )
    .dispatch()

// Specify queue and connection
sendWelcomeEmailJob
    .setUserId( rc.id )
    .setEmail(  rc.email )
    .onQueue(      "high-priority" )
    .onConnection( "redis" )
    .dispatch()

Production Patterns

Handler โ€” Dispatch on User Registration

function register( event, rc, prc ) {
    var user = userService.create( rc )

    // Dispatch async background jobs
    sendWelcomeEmailJob.setUserId( user.getId() ).setEmail( user.getEmail() ).dispatch()
    setupDefaultPreferencesJob.setUserId( user.getId() ).dispatch()

    // Respond immediately โ€” do not wait for jobs
    messagebox.success( "Registration successful! Check your email." )
    relocate( "dashboard" )
}

Chaining Jobs

// Job A dispatches Job B upon completion
class extends="cbq.models.AbstractJob" {

    property name="generateReportJob" inject="GenerateReportJob";

    function handle() {
        // ... do work ...

        // Dispatch next job in the chain
        generateReportJob
            .setReportId( reportId )
            .delay( 5 )
            .dispatch()
    }
}

Idempotent Job

class extends="cbq.models.AbstractJob" {

    property name="cache" inject="cachebox:default";

    function handle() {
        var lockKey = "job_processed_#orderId#"

        if ( cache.get( lockKey ) ) {
            return // Already processed โ€” skip
        }

        // Process order...
        processOrder( orderId )

        // Mark as done (TTL: 1 hour)
        cache.set( lockKey, true, 60 )
    }
}

Best Practices

  • Keep jobs small and focused โ€” one job should do one thing
  • Make jobs idempotent โ€” safe to run multiple times in case of retries
  • Never trust job payload without validation โ€” validate properties in handle() before use
  • Use the failed() hook to log failures and optionally alert via monitoring
  • Set appropriate timeout per job type โ€” short for emails, longer for PDF generation
  • Route by priority โ€” use high and low named queues with different worker counts
  • Avoid large payloads โ€” store IDs in the job, not entire entity serializations
  • Test job logic in unit tests โ€” call handle() directly without dispatching

Documentation