GRAILS.IO Avatar

Road to Grails 2.3: Async Support

With Grails 2.2, we introduced some basic support for accessing the Servlet 3.0 async APIs. However, we knew then that the Servlet 3.0 API wasn’t the programming model we wanted to expose to users. The reasons for this are:

  • The Servlet 3.0 APIs are servlet specific
  • Like the rest of the Servlet API they are low-level and verbose
  • The Servlet 3.0 API is just not very Groovy

Starting in Grails 2.3, we are introducing new APIs based around the notion of promises. A Promise is similar to a Java Future instance, but include a more user friendly exception handling model, useful features like chaining and the ability to attach listeners.

A basic example of promises in Grails 2.3 can be seen below

import static grails.async.Promises.*

def p1 = task { 2 * 2 }
def p2 = task { 4 * 4 }
def p3 = task { 8 * 8 }
assert [4,16,64] == waitAll(p1, p2, p3)

The above example creates 3 async tasks and then waits synchronously for them to complete. Of course, fitting asynchronous programming APIs into a full stack framework requires a lot more than exposing a nice API for programming concurrently.

From a Grails perspective, how do the Async APIs fit into controllers? How do they work with GORM? What about Transactions?

There is a whole bunch of considerations, and we’ve tried to resolve all of these within an elegant API.

With GORM, we have introduced a new “async” namespace, that allows all of the regular GORM query methods to a return a promise:

 
import static grails.async.Promises.*
def p1 = Person.async.get(1L)
def p2 = Person.async.get(2L)
def p3 = Person.async.get(3L)
def results = waitAll(p1, p2, p3)

Grails will automatically deal with the intricacies of opening a Hibernate session for the background thread, closing it when it is done and so on. You can even batch up multiple queries using the “task” method:

 
def promise = Person.async.task {
    withTransaction {
       def person = findByFirstName("Homer")
       person.firstName = "Bart"
       person.save(flush:true)    
    }
}
Person updatedPerson = promise.get()

For controllers the advantage of Servlet 3.0 Async is that using a modern container (such as Tomcat 7.0) the link between request, response and thread can be broken. By handling a request asynchronously you can return the request thread to the container, whilst keeping the response open in a non-blocking fashion. This makes modern containers far more scalable, in particular when dealing with long running requests.

With Grails 2.3 you can simply return a Promise from any action using the task method. The following example using the Yahoo Finance APIs:

 
def stock(String ticker) {
   task {
       ticker = ticker ?: 'GOOG'
       def url = new URL("http://download.finance.yahoo.com/d/quotes.csv?s=${ticker}&f=nsl1op&e=.csv")
       Double price = url.text.split(',')[-1] as Double
       render "ticker: $ticker, price: $price"
   }
}

You can also return an Async model, which is a map where the values are promises. The promises will be executed asynchronously and when ready the view rendered:

 
import static grails.async.Promises.*
…
def index() {
   tasks books: Book.async.list(),
         totalBooks: Book.async.count(),
         otherValue: {
           // do hard work
         }
}

For services it is quite typical to need both a synchronous and asynchronous version of your API. Unfortunately it is tedious and error prone to maintain both. To mitigate this problem Grails 2.3 features a DelegateAsync transformation which takes any normal Grails service and exposes asynchronously.

For example take the following service:

 
class BookService {    
    List findBooks(String title) {
      // implementation
    }
}

If you wish to expose this in an asynchronous manner simply create a new class that uses the DelegateAsync annotation:

 
import grails.async.*
class AsyncBookService {
   @DelegateAsync BookService bookService    
}

Every method from the original service will execute asynchronously returning a Promise:

 
AsyncBookService asyncBookService
def findBooks(String title) {
    asyncBookService.findBooks(title)
       .onComplete { List results ->
          println "Books = ${results}"    			
       }
}

If the service is transactional the Promise execution will wrapped in a transaction. If you use the @Transactional annotation then the annotation attributes defined in the annotation will be used when creating the transaction.

All of this is just a preview of what is to come (if you want to find out more checkout this commit to the docs). We’re inching closer to the first 2.3 milestone release, so stay tuned for more.

Replies

Likes

  1. y-u reblogged this from graemerocher
  2. graemerocher posted this

 

Reblogs