Search code examples
grailsgrails-ormmulti-tenantgrails3

Grails schema multitenancy mode: GORM listeners only working with default tenant


I'm adapting an existing Grails 3 project to a multi-tenant structure, using the schema mode provided by GORM, and I'm having trouble getting the GORM listeners to work when I specify a tenant.

My listener looks like this:

@CompileStatic
class VehicleListenerService {

    @Listener(Vehicle)
    void onPreInsertEvent(PreInsertEvent event) {
        println "*** Vehicle preInsert"
        event.entityAccess.setProperty('model', 'preInsert')
    }

    @Listener(Vehicle)
    void onPreUpdateEvent(PreUpdateEvent event) {
        println "*** Vehicle preUpdate"
        event.entityAccess.setProperty('model', 'preUpdate')
    }

}

So every time a vehicle is created or updated, its model should be changed to preInsert or preUpdate.

The current tenant is determined by the subdomain specified in the URL. If I access the app with no subdomain (via http://localhost:8080), the listener works as expected, but if I provide a subdomain (http://test.localhost:8080), the listener doesn't do anything, and the vehicle model doesn't change.

What do I have to do to make the GORM listener work with any tenant?

I've created a sample project (https://github.com/sneira/mtschema) which reproduces the error.


Solution

  • With help from the Grails Slack channel and some more research, I've come up with a solution to this.

    First, the listener service has to extend AbstractPersistenceEventListener:

    @CompileStatic
    class VehicleListenerService extends AbstractPersistenceEventListener {
    
        protected VehicleListenerService(Datastore datastore) {
            super(datastore)
        }
    
        @Override
        protected void onPersistenceEvent(AbstractPersistenceEvent event) {
            String newModel = 
                    event.eventType == EventType.PreInsert ? 'preInsert' : 'preUpdate'
            event.entityAccess.setProperty('model', newModel)
        }
    
        @Override
        boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
            boolean supportsEvent = eventType.isAssignableFrom(PreInsertEvent) ||
                    eventType.isAssignableFrom(PreUpdateEvent)
            return supportsEvent
        }
    
    }
    
    

    Now we can create a service instance for each schema (except for the default) in Bootstrap.groovy, and add it to our app:

    def init = { servletContext ->
        def ctx = grailsApplication.mainContext
        ['TEST', 'TEST2'].each { String name ->
            HibernateDatastore ds = hibernateDatastore.getDatastoreForConnection(name)
            VehicleListenerService listener = new VehicleListenerService(ds)
            ctx.addApplicationListener(listener)
        }
    }
    

    I've uploaded the complete code to https://github.com/sneira/mtschema/tree/listeners.