3. Service
Business 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.
4. Create Service
grails> 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() {
}
}
5. Session vs Transaction
User 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.
6. Scope
> singleton (default), prototype, request, session
class SomeUsefulService {
// this is a request scoped service
static scope = 'request'
}
10. @Transactional annotation
Service 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.
12. No Transaction & ReadOnly
@NonTransactional
class EmailService {
//OR static transactional = false
….
}
@Transactional(readOnly = true)
class ReportService {
….
}
13. withTransaction
package 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()
}
}
}
15. Transaction Propagation :(
// Doesn’t work well without additional configuration
@Transactional
void someMethod(...) {
// do some work ...
storeAuditData(...)
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
void storeAuditData(...) {
//
}
http://techbus.safaribooksonline.com/book/programming/9781449324513/4dot-
spring/_transactional_services_html
17. Exceptions and Validations
class 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)
}
}
}
18. Exception on Save
def p = Person.get(1)
try {
p.save(failOnError: true)
}
catch (ValidationException e) {
// deal with exception
p.errors.allErrors.each {
println it
}
}
19. LazyInitializationException
When 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}"
}
} }
20. 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)
}
}
}
26. Asynchronous - Promise
import 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"
}
27. Synchronous - Promise
import static java.util.concurrent.TimeUnit.*
import static grails.async.Promises.*
Promise p = task {
// Long running task
}
…. other tasks
// block until result is called
def result = p.get()
// block for the specified time
def result = p.get(1,MINUTES)
33. Async and the Session
When 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