0
votes

I want to implement the concept of Trigger objects in Grails for my domain classes. Grails allows me to define methods like beforeInsert() in my domain classes to handle insert, update and delete events, similar to how SQL databases have the concept of database triggers. But to separate concerns I'd like to move all of the trigger logic outside of the domain class.

Ideally I can simply define a static list of trigger classes in my domain class and then iterate through those in the beforeInsert() method. Sample code shown below.

static beforeInsertTriggers = [AccountNameTrigger, AccountDumpTrigger]

def beforeInsert() {
    for (Class<Trigger> triggerClass : beforeInsertTriggers) {
        triggerClass.newInstance().using(this).execute()
    }
}

I've created a sample Grails 2.5.4 project on GitHub illustrating what I'm trying to do. But the problem is that the triggers literally are standalone islands of logic without the ability to autowire services. How can I better set up this Trigger pattern or something similar? So I may autowire services and other beans into the Trigger instances?

I am also trying to keep the design simple and readable.

  • Avoid cluttering the domain class with fields and properties that are not related to the data model. To me this means no autowired beans in the domain class.
  • Define the list of triggers using the trigger class names (or bean names, if need be). This may be overly idealistic, but hey...

For what it's worth, part of my inspiration also comes from how Salesforce implements triggers as distinct units of self-contained code.

1

1 Answers

0
votes

Register your Triggers as Singleton Beans and there you can inject other services/beans. You can create your custom beans via resources.groovy.

Lets take the example of AccountDumpTrigger. Lets make a simple change to it:

package grails.domain.trigger.demo.triggers

import grails.domain.trigger.demo.Account
import org.codehaus.groovy.grails.commons.GrailsApplication

/**
 * Created by marty on 6/25/16.
 */
class AccountDumpTrigger extends AbstractTrigger<Account> {

    GrailsApplication grailsApplication

    @Override
    void execute() {
        println grailsApplication.isInitialised()
        println resource.dump()
    }
}

And code in resources.groovy or in your plugin's doWithSpring closure:

accountDumpTrigger(grails.domain.trigger.demo.triggers.AccountDumpTrigger) { bean ->
    bean.factoryMethod = 'getInstance'
    /*
     either refer each bean individually  or you can use:
     bean.autowire = "byType"
       OR
     bean.autowire = "byName"
    */
    grailsApplication = ref("grailsApplication")
}

And inside your domain:

static beforeInsertTriggers = [AccountDumpTrigger]

def beforeInsert() {
    for (Class<Trigger> triggerClass : beforeInsertTriggers) {
        triggerClass.instance.using(this).execute()
    }
}

And instead of writing your code inside beforeInsert you can also do the same by registering an implementation of AbstractPersistenceEventListener. This way you don't have to repeat your code. Also yo can move it to a parent class.