Date post: | 13-Aug-2015 |
Category: |
Software |
Upload: | husain-dalal |
View: | 114 times |
Download: | 4 times |
Grails Services, Transactions & Async calls
Service
ServiceBusiness logic goes in services.
Don’t use controllers to do any heavy lifting. They are designed only for controlling application flow and doing data marshaling. All your data access and business logic should happen in transactional services.
Also avoid much logic in Domain classes. Validation logic there can be limited to basic constraints and basic postUpdate and postCreate methods.
Create Servicegrails> create-service com.netflix.Contract
| Created file grails-app/services/com/netflix/ContractService.groovy
| Created file test/unit/com/netflix/ContractServiceTests.groovy
package com.netflix
class ContractService {
def serviceMethod() {
}
}
Session vs TransactionUser Session: Corresponds to time within which the user is logged in to the system.
Hibernate Session: Associated with JDBC Connection.
Hibernate Transaction: ACID compliant boundary for unit of work.
Scope> singleton (default), prototype, request, session
class SomeUsefulService {
// this is a request scoped service
static scope = 'request'
}
Service Injectionclass ContractController {
ContractService contractService
...
}
No need for AutoWire annotation.
Command [email protected] LoginCommand { def loginService
String username String password
static constraints = { username validator: { val, obj -> obj.loginService.canLogin(obj.username, obj.password) } }}
Transaction
@Transactional annotationService methods are Transactional by default. However best practice is to add @Transactional annotation to each service
Transaction annotation is similar to Spring’s @Transactional annotation
The @Transactional annotation on the class ensures that all public methods in a service are transactional.
Annotating a service method (and not at class) with @Transactional disables the default Grails transactional behavior for that method. so if you use any annotations you must annotate all methods that require transactions.
Exampleimport grails.transaction.Transactional
@Transactionalclass BookService { @Transactional(readOnly = true) def listBooks() { Book.list() }
def updateBook() { // … }}
No Transaction & ReadOnly@NonTransactional
class EmailService {
//OR static transactional = false
….
}
@Transactional(readOnly = true)
class ReportService {
….
}
withTransactionpackage com.netflix
class ContractService {
// turn off automatic transaction management
static transactional = false
void someServiceMethod() {
Contract.withTransaction {TransactionStatus tx ->
// do some work with the database….
// if the transaction needs to be rolled back, call setRollbackOnly()
tx.setRollbackOnly()
}
}
}
Savepointdef save() {
Album.withTransaction { status ->
def album = Album.get(params.id)
album.title = "Changed Title"
album.save(flush:true)
def savepoint = status.createSavepoint()
...
// something goes wrong
if(hasSomethingGoneWrong()) {
status.rollbackToSavepoint(savepoint)
// do something else
...
} } }
Transaction Propagation :(// Doesn’t work well without additional configuration@Transactionalvoid someMethod(...) { // do some work ... storeAuditData(...)}
@Transactional(propagation=Propagation.REQUIRES_NEW)void storeAuditData(...) { //}http://techbus.safaribooksonline.com/book/programming/9781449324513/4dot-spring/_transactional_services_html
Error Handling
Exceptions and Validationsclass AuthorService { void updateAge(id, int age) { def author = Author.get(id) author.age = age if (author.isTooOld()) { throw new AuthorException("too old", author) } if (!author.validate()) { throw new ValidationException("Author is not valid", author.errors) } }}
Exception on Savedef p = Person.get(1)try { p.save(failOnError: true)}catch (ValidationException e) { // deal with exception
p.errors.allErrors.each { println it }}
LazyInitializationExceptionWhen a transaction is rolled back the Hibernate session used by GORM is cleared. This means any objects within the session become detached and accessing uninitialized lazy-loaded collections will lead to LazyInitializationException
class AuthorController { def authorService def updateAge() { try { authorService.updateAge(params.id, params.int("age")) } catch(e) { render "Author books ${e.author.books}" } } }
Solution...class AuthorService { void updateAge(id, int age) { def author = Author.findById(id, [fetch:[books:"eager"]]) author.age = age if (author.isTooOld()) { throw new AuthorException("too old", author) } }}
Sample
Asynchronous
Various ways...
● Event mechanism using Platform Core plugin
● JMS Messaging Queues Plugin● Quartz scheduling Plugin● Asynchronous Programming
Asynchronous - Promiseimport static java.util.concurrent.TimeUnit.*import static grails.async.Promises.*Promise p = Promises.task {
// Long running task}p.onError { Throwable err ->
println "An error occured ${err.message}"}p.onComplete { result -> println "Promise returned $result"}
Synchronous - Promiseimport static java.util.concurrent.TimeUnit.*import static grails.async.Promises.*Promise p = task {
// Long running task}…. other tasks// block until result is calleddef result = p.get()// block for the specified timedef result = p.get(1,MINUTES)
PromiseListimport static grails.async.Promises.*import grails.async.PromiseListPromiseList promiseList = tasks([{ 2 * 2 }, { 4 * 4}, { 8 * 8 }])//... some other processesassert [4,16,64] == promiseList.get()
PromiseMapimport grails.async.*
PromiseMap map = new PromiseMap()map['one'] = { 2 * 2 }map['two'] = { 4 * 4 }map['three'] = { 8 * 8 }//Async callmap.onComplete { Map results -> assert [one:4,two:16,three:64] == results}
DelegateAsync Transformation//Sync serviceclass BookService { List<Book> findBooks(String title) { // implementation }}
//Async Serviceimport grails.async.*class AsyncBookService { @DelegateAsync BookService bookService}
DelegateAsync call//Async service call AsyncBookService asyncBookServicedef findBooks(String title) { asyncBookService.findBooks(title) .onComplete { List results -> println "Books = ${results}" }}
Asynchronous GORMimport static grails.async.Promises.*Person.async.list().onComplete { List results -> println "Got people = ${results}"}
PromiseList p = Person.async.getAll(1L, 2L, 3L)List results = p.get()
Promise p1 = Person.async.findByFirstName("Homer")Promise p2 = Person.async.findByFirstName("Bart")Promise p3 = Person.async.findByFirstName("Barney")results = waitAll(p1, p2, p3)
Async and the SessionWhen using GORM async each promise is executed in a different thread. Since the Hibernate session is not concurrency safe, a new session is bound per thread.This means you cannot save objects returned from asynchronous queries without first merging them back into session.
def promise = Person.async.findByFirstName("Homer")def person = promise.get()person.merge()person.firstName = "Bart"
In general it is not recommended to read and write objects in different threads and you should avoid this technique unless absolutely necessary. Also fetch the dependent objects eagerly to avoid LazyInitializationException
Quartzplugins { ... compile ":quartz:1.0.1" ...}
//New commands
grails create-jobgrails install-quartz-config
grails create-job com.netflix.SupplierImport
spock
Testing Services