3 Configure Dedicated Job Storage

JobRunr needs one durable writer data source. Do not point JobRunr at a read replica: workers both read and write job state. The JobRunr storage documentation explains the storage requirements.

The sample separates application persistence from JobRunr persistence. Grails names the default bean dataSource and the named jobrunr source dataSource_jobrunr:

grails-app/conf/application.yml
grails:
    profile: web
    codegen:
        defaultPackage: example.grails
    gorm:
        reactor:
            events: false

dataSource:
    dbCreate: create-drop
    url: jdbc:h2:mem:application;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    driverClassName: org.h2.Driver
    username: sa
    password: ''

dataSources:
    jobrunr:
        dbCreate: none
        url: jdbc:h2:mem:jobrunr;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
        driverClassName: org.h2.Driver
        username: sa
        password: ''

hibernate:
    cache:
        queries: false
        use_second_level_cache: false
        use_query_cache: false

jobrunr:
    jobs:
        default-number-of-retries: 3

server:
    port: 8080

environments:
    development:
        jobrunr:
            background-job-server:
                enabled: true
                worker-count: 2
                poll-interval-in-seconds: 5
            dashboard:
                enabled: true
                port: 8000
    test:
        jobrunr:
            background-job-server:
                enabled: true
                worker-count: 1
                poll-interval-in-seconds: 5
            dashboard:
                enabled: false
    production:
        jobrunr:
            database:
                skip-create: true
            background-job-server:
                enabled: false
            dashboard:
                enabled: false
            miscellaneous:
                allow-anonymous-data-usage: false

With multiple data sources, relying on type-only auto-configuration can be ambiguous. Instead, the sample supplies an explicit StorageProvider and qualifies the dedicated source. It also installs the JobMapper eagerly:

src/main/groovy/example/grails/jobrunr/JobRunrStorageConfig.groovy
package example.grails.jobrunr

import javax.sql.DataSource
import org.jobrunr.jobs.mappers.JobMapper
import org.jobrunr.spring.autoconfigure.JobRunrProperties
import org.jobrunr.storage.StorageProvider
import org.jobrunr.storage.StorageProviderUtils.DatabaseOptions
import org.jobrunr.storage.sql.common.SqlStorageProviderFactory
import org.jobrunr.utils.mapper.jackson.JacksonJsonMapper
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class JobRunrStorageConfig {
    @Bean
    StorageProvider storageProvider(
        @Qualifier('dataSource_jobrunr') DataSource jobrunrDataSource,
        JobRunrProperties jobRunrProperties
    ) {
        StorageProvider storageProvider = SqlStorageProviderFactory.using(
            jobrunrDataSource,
            jobRunrProperties.database.tablePrefix,
            jobRunrProperties.database.skipCreate ? DatabaseOptions.SKIP_CREATE : DatabaseOptions.CREATE
        )
        storageProvider.setJobMapper(new JobMapper(new JacksonJsonMapper()))
        storageProvider
    }
}

The configuration reads jobrunr.database.table-prefix and jobrunr.database.skip-create from JobRunr properties. Set a table prefix when JobRunr must share a schema with other applications or deployments. With skip-create: false, the provider creates its tables, which is useful for this guide’s disposable H2 storage. With skip-create: true, it uses DatabaseOptions.SKIP_CREATE and does not create tables at application startup.

Before any production node starts with skip-create: true, apply the JobRunr schema migrations for the configured database and table prefix. Missing tables must fail startup rather than allowing an application instance to take ownership of production DDL. Production schema management is covered in Production Operations and OSS Boundaries.

Grails must see the configuration class and its components. The application imports the configuration and scans the package containing the listener and handlers:

grails-app/init/example/grails/Application.groovy
package example.grails

import example.grails.jobrunr.JobRunrStorageConfig
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
import org.grails.datastore.gorm.boot.autoconfigure.HibernateGormAutoConfiguration
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Import

@Import([HibernateGormAutoConfiguration, JobRunrStorageConfig])
@ComponentScan('example.grails')
class Application extends GrailsAutoConfiguration {
    static void main(String[] args) {
        GrailsApp.run(Application, args)
    }
}
  Get the Code