After wrestling with JMS one too many times we decided to take a look at Spring's event framework instead. It turns out it's extremely easy to use for these kinds of asynchronous notifications in a Grails application.
Essentially any artefact can publish an event to the Spring application context. A simple publishing service can be implemented like this:
import org.springframework.context.* class EventService implements ApplicationContextAware { boolean transactional = false ApplicationContext applicationContext void publish(ApplicationEvent event) { println "Raising event '$event' in thread ${Thread.currentThread().id}" applicationContext.publishEvent(event) } }So a Grails domain class can then do something like this:
def eventService void afterInsert() { eventService.publish(new DocumentEvent(this, "created")) } void afterUpdate() { eventService.publish(new DocumentEvent(this, "updated")) } void afterDelete() { eventService.publish(new DocumentEvent(this, "deleted")) }Grails services make ideal ApplicationListener implementations. As services are singleton Spring beans they are automatically discovered by Spring's event system without any configuration required. For example:
import org.springframework.context.* class EventLoggingService implements ApplicationListener<DocumentEvent> { boolean transactional = false void onApplicationEvent(DocumentEvent event) { println "Recieved event '$event' in thread ${Thread.currentThread().id}" } }Of course, multiple listeners can respond to the same events.
If you run the code you will notice that by default Spring's event system processes events synchronously. The EventService and ApplicationListener will print out the same Thread id. This is not ideal if any of the listener implementations might take any time. Luckily it's easy to override the ApplicationEventMulticaster bean in
resources.groovy
so that it uses a thread pool:import java.util.concurrent.* import org.springframework.context.event.* beans = { applicationEventMulticaster(SimpleApplicationEventMulticaster) { taskExecutor = Executors.newCachedThreadPool() } }Running the code again will show the event being published in one thread and consumed in another. If you have multiple listeners each one will be executed in its own thread.
Oddly, I would have thought it was possible to override the taskExecutor property of the default ApplicationEventMulticaster in
Config.groovy
using Grails' property override configuration, but I found the following didn't work:beans { applicationEventMulticaster { taskExecutor = Executors.newCachedThreadPool() } }
22 comments:
Hello!
Just wondering(noob question): can I use the same features through a simple web server like tomcat? Or only through spring application server?
The code is based on the Spring framework API but yeah it runs in a standard Grails app on Tomcat.
Would you recommend this technique for implementing simple work flow?
For example, I have a record that starts in worker queue. I want to check when certain fields have been completed and then move the record into the supervisor queue.
In that case what would fire the event? Perhaps the onUpdate of the domain class? Sounds reasonable I guess. Quartz would be another option.
Robert,
Thanks a lot for posting. I think this will resolve a design issue I've been trying to figure out for a while.
Jean
Great Post!
Another skill, you can define a property named grailsApplication in your Service to get the Instance of GrailsApplication. Then get ApplicationContext from it. The following is a sample:
class EventService {
def grailsApplication
boolean transactional = false
void publish(def event) {
println "Raising event '$event' in thread ${Thread.currentThread().id}"
grailsApplication.mainContext.publishEvent(event)
}
}
cheers
What didn't work about using property overrides?
@Luke: Purely that the taskExecutor property wasn't overridden. I've certainly used property override config successfully at other times so I'm not sure what the problem is.
In the domain class, you are calling publish() with new DocumentEvent. Did you mean ApplicationEvent? If not, what is the import statement for DocumentEvent? I'm new to this stuff and it's not quite clear to me :^)
I'm assuming DocumentEvent is some kind of class that extends ApplicationEvent.
Hello,
It seems that once you override the ApplicationEventMulticaster bean in resources.groovy(to set it to async), there is no longer support for lazy-loading properties of domain objects, hence you get LazyInititExceptions.
Anyone has any ideea about this?
@cristiancalugaru yes, I wrote the Grails Spring Events plugin based on this blog post & that includes a mechanism for ensuring the asynchronous event processing can access a Hibernate session.
Hello Rob, thanks for your answer.
To be more precise, I have the following situation:
From a grails service, I'm doing:
applicationContext.publishEvent(new GenericIdEvent(id));
Then, I have an AsyncProcessingService, which implements ApplicationListener.
In it's onApplicationEvent() method, I'm fetching an entity based on the id I have published above.
Then when I want to get entity.myInnerProperty, I m getting the famous org.hibernate.LazyInitializationException: could not initialize proxy - no Session.
I have configured the threadpool in resources.groovy, just like in the example:
applicationEventMulticaster(SimpleApplicationEventMulticaster) {
taskExecutor = Executors.newCachedThreadPool()
}
The listener - AsyncProcessingService has the boolean transactional = false,
while the service that published the event has it set to true.
Am i missing something very obvious here?
Thanks
which version of the plugin are you using & which version of Grails?
had to explicitly register my service for grails 2.1.. Only then were the events received. However with grails 2.2 they have built awesomeness with implicit event transmission capacity for controllers and even integration with spring integration
Good Day Rob,
I am using the spring-events plugin in a Grails 2.2.4 application. I was able to configure it per your plugin README, and everything is working fine with the default retryPolicy. I would like to execute some code on the final failed attempt of a retryPolicy. I have a database domain object that gets populated via the 'event.source'. I would like to update the field on the last failed attempt, but I am not sure how to do that.
Thanks!
Hi Rob, I'm running Grails 2.2.0 and have been using the technique outlined in your article and it's been working great. For some reason I never noticed that it wasn't actually executing asynchronously until I bumped into a long running task the other day and started to investigate.
Coming back to your article I simply configured the resources.groovy to what you have outlined but now I'm getting really bizarre issues like domain objects rejecting validation because dateCreated is null (an attribute that Grails sets itself). Turn of the resources.groovy change and everything goes back to normal. Saves work no lack of session Exceptions.
I'm just wondering if I'm using an outdate technique now and should be perhaps using some other setting. This article is over 4 years old.
Can I track events like login , logout and login failure?
this is amazing site 바카라사이트
TV commercials are one of the best ways to grow your business.
The first-ever One Year MBA program in Digital Enterprise Management (DEM) introduces candidates with work experience to management concepts and leadership styles in the emerging digital business enterprises.
browse around these guys why not find out more Website her explanation read the article weblink
Post a Comment