The GroovySpringBootBatchGormGroovyDslBeanFactory

See spring-boot-batch-sample at github for the updated source code.

I was recently working on setting up a not-so-trivial Spring Batch application and wanted to use SpringBoot, because of its embeded-web container capabilities. I could have used Grails and Spring Batch plugin, but wanted to give an opportunity for our Ops/DevOps team a peek at running and maintaining apps via plain “java -jar”. I hadn’t used Spring since 2009 (switching between SharePoint and Grails). Xml is no more a necessity for Spring and it has good support for java annotation configuration. But I am not very comfortable with annotation oriented programming. Xml provided the separation of wiring dependencies from code, but its verbose. Annotations are less verbose, but allows to mix configurations, logic and code, which I feel can spiral out of control pretty soon. I still feel wiring dependencies independently of “code” is a very desirable feature for large applications. Both xml and annotations seem to be at opposite ends of the spectrum.

Fortunately there is a middle ground. SpringBoot provides support for Groovy beans dsl, via the GroovyBeanDefinitionReader. Groovy bean dsls solve the problems of xml and annotations: DI wiring + concise readable syntax + some logic (eg environment-based). But this comes at a price of loosing type-safety. I am surprised that groovy dsl has not yet gone mainstream with Spring apps. If Spring comes with a Groovy DSLD schema (may be there is one already?), it could be a killer feature. For example, Spring Integration is already offering a dsl based “workflow”, which is pretty elegant to read, write and maintain.

I was also new to SpringBatch so it took a while to get them all wired up. So here is a starting point, if you want to use Groovy lang + SpringBoot + SpringBatch + Groovy DSL + Gorm. I haven’t figured out dsl equivalents of @EnableScheduling and @Scheduled yet.

Part 1: appcontext.groovy

//Note that config is not defined as a bean, but directly evaluated and then injected into other beans
//the 'grailsApplication' equivalent
ConfigObject configObject = new ConfigSlurper().parse(Config)

//Note the syntax beans {} not beans = {} like in Grails resources.groovy
beans {

	xmlns([ctx: 'http://www.springframework.org/schema/context', batch: 'http://www.springframework.org/schema/batch'])
	ctx.'component-scan'('base-package': 'org.mypackage')
	ctx.'annotation-config'()

	myController(MyController) {
		config = configObject 
	}

	myService(MyService) {
		config = configObject 
  }

  //MyItemReader implements ItemReader
	myItemReader(MyItemReader) { bean ->
		bean.initMethod = 'init' //required if initializing some data from external dao
		bean.scope = 'step' //for job restartability
	}

	myItemProcessor(MyItemProcessor) {
		myService = ref('myService')
	}

	myItemWriter(FlatFileItemWriter) {
		lineAggregator = new DelimitedLineAggregator(delimiter: ',', fieldExtractor: new BeanWrapperFieldExtractor(names: ["id", "title"]))
		resource = '/apps/springboot/myproject/output'
	}

  //create a job
	batch.job(id: 'job1') {
		batch.step(id: 'step1') {
			batch.tasklet {
				batch.chunk(
					reader: 'myItemReader',
					writer: 'myItemWriter',
					processor: 'myItemProcessor',
					'commit-interval': 10
				)
			}
		}
	}

  //the following beans are minimum mandate because there is no equivalent of xml's <batch:job-repository /> in groovy dsl
  //http://stackoverflow.com/questions/23436477/groovy-bean-syntax-for-spring-batch-job-repository
  //thanks to https://github.com/johnrengelman/grails-spring-batch/blob/master/SpringBatchGrailsPlugin.groovy for the bean definitions
	jobRepository(MapJobRepositoryFactoryBean) {
		transactionManager = ref('transactionManager')
	}

	jobRegistry(MapJobRegistry) { }

	jobLauncher(SimpleJobLauncher) {
		jobRepository = ref('jobRepository')
		taskExecutor = { SyncTaskExecutor executor -> }
	}

	jobExplorer(JobExplorerFactoryBean) {
    //dataSource is auto-configured
		dataSource = ref('dataSource')
	}

	jobOperator(SimpleJobOperator) {
		jobLauncher = ref('jobLauncher')
		jobRepository = ref('jobRepository')
		jobRegistry = ref('jobRegistry')
		jobExplorer = ref('jobExplorer')
	}

}

Part 2: Application and Scheduler

@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableScheduling
class MyJobApplication {

	private static final Logger logger = LoggerFactory.getLogger(MyJobApplication.class.getName())

	@Autowired
	JobLauncher jobLauncher

	@Autowired
	Job myJob

//  You can also create configObject bean like this and refer back in beans.groovy using ref('config')
//	@Bean(name="config")
//	ConfigObject configObject() {
//		return new ConfigSlurper().parse(Config)
//	}

	
  @Scheduled(fixedDelayString = '${myJobFixedDelay}', initialDelayString = '${myJobInitialDelay}')
	public void startMyJob() {
		logger.info "startMyJob()"
    //Add time if your job runs repeatedly on different parameters - this will make it an unique entry in the batch-job tables
		JobParameters jobParameters = new JobParametersBuilder().addLong("time",System.currentTimeMillis()).toJobParameters()
		jobLauncher.run(myJob, jobParameters)
	}

	public static void main(String[] args) {
		logger.info "Starting MyJobApplication..."
		Object[] sources = [MyJobApplication.class, new ClassPathResource("appcontext.groovy")]
		SpringApplication.run(sources, args);
	}
}

Part 3: Datasource

Datasource bean is autoconfigured via @EnableAutoConfiguration and if you define the application.properties (or application.yaml). Just add the right driver in your build.gradle. If you dont specify any, hsql db is used.

Part 4: build.gradle

buildscript {
	ext {
		springBootVersion = '1.0.2.RELEASE'
		spockVersion = '0.7-groovy-2.0'
	}
	repositories {
		mavenLocal()
		mavenCentral()
		maven { url "http://repo.spring.io/libs-snapshot" }
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}

apply plugin: 'groovy'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar {
	baseName = 'myapp'
	version = '0.1'
}

repositories {
	mavenCentral()
	mavenLocal()
	maven { url "http://repo.spring.io/libs-snapshot" }
}

dependencies {
	compile("org.springframework.boot:spring-boot-starter-web")
	//if you want to use Jetty, instead of Tomcat, replace the above line with the next two lines
	//compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}") { exclude module: "spring-boot-starter-tomcat" }
	//compile("org.springframework.boot:spring-boot-starter-jetty:0.5.0.M2")
	compile("org.springframework.boot:spring-boot-starter-actuator")
	compile("org.springframework.boot:spring-boot-starter-batch")
	compile("org.springframework.boot:spring-boot-starter-logging")
	compile("org.springframework.boot:spring-boot-starter-jdbc")

  compile("org.codehaus.groovy:groovy-all:2.2.2")
	
  compile("org.springframework:spring-orm:4.0.3.RELEASE")
  //For those Grails guys, just throw in the new and shiny standalone gorm dependency
	compile("org.grails:gorm-hibernate4-spring-boot:1.0.0.RC3") {
    //currently brings in spring-orm:3.2.8, exclude it and explicitly include new one above
		exclude module: 'spring-orm'
	}

	testCompile "org.springframework.boot:spring-boot-starter-test"
	testCompile "org.spockframework:spock-core:${spockVersion}"
	testCompile "org.spockframework:spock-spring:${spockVersion}"
}

task wrapper(type: Wrapper) {
	gradleVersion = '1.11'
}

A few more features for the future:

1. Defining multiple datasources in groovy dsl
2. Using spring-loaded for hot-swapping runtime (as of now I can’t get this to work with Intellij Idea)
3. Using Spring-batch-admin to control jobs via UI

About these ads

2 Responses to The GroovySpringBootBatchGormGroovyDslBeanFactory

  1. Really cool article, thank you !
    Spring loaded works for me under intelliJ. You just have to add “-javaagent:/path/to/springloaded.jar -noverify” as a vm option when you run you main method in debug.
    But I guess you already tried that

    Like

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 189 other followers

%d bloggers like this: