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
}
}
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:
The service first persists the delivery and publishes a small event within its GORM transaction:
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
}
}
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:
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:
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.