5 Enqueue After Commit

The job must not run for a delivery that rolls back. The delivery model records the business state and progress that the job updates:

grails-app/domain/example/grails/Delivery.groovy
package example.grails

class Delivery {
    String reference
    String status = 'PENDING'
    Integer progress = 0
    Date completedAt

    static constraints = {
        reference nullable: false, blank: false, unique: true
        status blank: false
        completedAt nullable: true
    }
}

The service first persists the delivery and publishes a small event within its GORM transaction:

grails-app/services/example/grails/DeliveryService.groovy
package example.grails

import grails.compiler.GrailsCompileStatic
import grails.gorm.transactions.Transactional
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.ApplicationEventPublisher

@GrailsCompileStatic
@Transactional
class DeliveryService {
    @Autowired
    ApplicationEventPublisher applicationEventPublisher

    Delivery create(String reference) {
        Delivery delivery = new Delivery(reference: reference)
        if (!delivery.validate()) {
            return delivery
        }
        delivery.save(failOnError: true, flush: true)
        applicationEventPublisher.publishEvent(new DeliveryCreatedEvent(delivery.id))
        delivery
    }
}
src/main/groovy/example/grails/DeliveryCreatedEvent.groovy
package example.grails

import groovy.transform.CompileStatic

@CompileStatic
class DeliveryCreatedEvent {
    final Long deliveryId

    DeliveryCreatedEvent(Long deliveryId) {
        this.deliveryId = deliveryId
    }
}

The listener uses Spring’s transaction-bound event support to run only after that transaction commits, then enqueues the request:

src/main/groovy/example/grails/DeliveryJobEnqueuer.groovy
package example.grails

import example.grails.jobrunr.ProcessDeliveryJobRequest
import grails.compiler.GrailsCompileStatic
import org.jobrunr.scheduling.JobRequestScheduler
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import org.springframework.transaction.event.TransactionPhase
import org.springframework.transaction.event.TransactionalEventListener

@Component
@GrailsCompileStatic
class DeliveryJobEnqueuer {
    @Autowired
    JobRequestScheduler jobRequestScheduler

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    void enqueue(DeliveryCreatedEvent event) {
        jobRequestScheduler.enqueue(new ProcessDeliveryJobRequest(event.deliveryId))
    }
}

Grails does not register Spring’s TransactionalEventListenerFactory through @EnableTransactionManagement, so declare it explicitly or the transactional listener is silently ignored:

grails-app/conf/spring/resources.groovy
import org.springframework.transaction.event.TransactionalEventListenerFactory

beans = {
    transactionalEventListenerFactory(TransactionalEventListenerFactory)
}

This is a best-effort OSS pattern, not an atomic cross-database transaction. A process can fail after the GORM commit but before enqueueing. For business-critical delivery, write an application-managed transactional outbox in the application transaction and dispatch it reliably, or evaluate JobRunr Pro’s transaction plugin. The transaction plugin is a Pro feature and is intentionally not used by this sample.

  Get the Code