3
votes

I have a Grails project with multiple Domain Classes, and I want to make a persistence service as reusable as possible by only having one save() inside of it. To try and achieve this I have done the following in my project.

//PersistenceService.groovy

@Transactional
class PersistenceService {
def create(Object object) {        
    object.save flush: true
    object
}

//BaseRestfulController

class BaseRestfulController extends RestfulController {

def persistenceService

def save(Object object) {
    persistenceService.create(object)
}

//BookController

class BookController extends BaseRestfulController {

private static final log = LogFactory.getLog(this)

static responseFormats = ['json', 'xml']

BookController() {
    super(Book)
}

@Transactional
def save(Book book) {
    log.debug("creating book")

    super.save(book)

}

So basically I have a bunch of domains for example Author etc, each with their own controller similar to the bookController. So is there a way to reuse the service for persistence like I am trying above?

Thank you

1
I think it is doable but you have to change your signature of method create(Object object) to create(def object), and the save(Object object) to save(def object) in your BaseRestfulController class.James Zhang
def is the same as Object or no type info in the method signature: the 3 methods are exactly the same: create(o) === create(Object o) === create(def o)injecteer
@user2816352 I fail to understand what you are trying to achieve hereinjecteer

1 Answers

3
votes

I'm doing something similar, but mainly because all my entities are not actually removed from the database but rather "marked" as removed. For several apps you need such an approach since it's critical to prevent any kind of data loss.

Since most databases do not provide support for this scenario, you can't rely on foreign keys to remove dependent domain instances when removing a parent one. So I have a base service class called GenericDomainService which has methods to save, delete (mark), undelete (unmark).

This service provides a basic implementation which can be applied to any domain.

class GenericDomainService {

def save( instance ) {
    if( !instance || instance.hasErrors() || !instance.save( flush: true ) ) {
        instance.errors.allErrors.each {
            if( it instanceof org.springframework.validation.FieldError ) {
                log.error "${it.objectName}.${it.field}: ${it.code} (${it.rejectedValue})"
            }
            else {
                log.error it
            }
        }
        return null
    }
    else {
        return instance
    }
}

def delete( instance, date = new Date() ) {
    instance.dateDisabled = date
    instance.save( validate: false, flush: true )
    return null
}

def undelete( instance ) {
    instance.dateDisabled = null
    instance.save( validate: false, flush: true )
    return null
}

}

Then, in my controller template I always declare two services: the generic plus the concrete (which may not exist):

def ${domainClass.propertyName}Service
def genericDomainService

Which would translate for a domain called Book into:

def bookService
def genericDomainService

Within the controller methods I use the service like:

def service = bookService ?: genericDomainService
service.save( instance )

Finally, the service for a given domain will inherit from this one providing (if needed) the custom logic for these actions:

class BookService extends GenericDomainService {

def delete( instance, date = new Date() ) {
    BookReview.executeUpdate( "update BookReview b set b.dateDisabled = :date where b.book.id = :bookId and b.dateDisabled is null", [ date: date, bookId: instance.id ] )
    super.delete( instance, date )
}

def undelete( instance ) {
    BookReview.executeUpdate( "update BookReview b set b.dateDisabled = null where b.dateDisabled = :date and b.book.id = :bookId", [ date: instance.dateDisabled, bookId: instance.id ] )
    super.undelete( instance )
}

}

Hope that helps.