Artifact Namespaces

jeffbrown edited this page Sep 4, 2012 · 22 revisions

Overview

Up through Grails 2.1.x we have no good way of managing artifact conflicts. Artifact conflicts include things like controllers or services with the same name in separate packages. For example an application may not contain both com.demo.UserController and com.demo.admin.UserController. Within an application this can be managed by simply naming them differently. The bigger problem comes from using multiple plugins that provide artifacts with the same name.

Below are proposed changes for Grails 2.2.

We should identify places where the plugin name may be used effectively as a namespace for artifacts. All places where artifact names are used are candidates for attention.

Controllers

When referring to a controller from a tag like g:link, specifying a plugin name may disambiguate the reference:

<g:link controller="user" plugin="springSecurity">Manage Users</g:link>

Methods that support a controller name in the form of a named argument should support a plugin name.

class DemoController {
    def index() {
        redirect controller: 'user', action: 'list', plugin: 'springSecurity'
    }
}

Services

Instances of service artifacts are automatically added to the spring application context. The bean name is derived from the class name so an instance of com.demo.admin.UserService is added to the application context with the bean name userService. For services provided by a plugin then bean name may be prefixed with a camel case version of the plugin name. For example, if the springSecurity plugin provides com.demo.admin.UserService then the bean name would be springSecurityUserService. It may be beneficial to alias the bean to userService if and only if there is no other UserService service anywhere in the application. That alias would make this more of a non-breaking change as there shouldn't be applications out there now that have multiple services with the same name so the alias would be in place for all of those situations. If the service class name begins with the plugin name then the additional prefix will not be added. For example, if the springSecurity plugin provides a service named SpringSecurityUserService, the bean name would be springSecurityUserService, not springSecuritySpringSecurityUserService.

Tag Libraries

Tag Libraries already support a namespace property. If that property is defined it should be respected. We need to figure out if it makes sense to use the plugin name as a default namespace for tags that don't express a namespace and if so, should that be in addition to or instead of putting those tags in the g namespace.

Domain Classes

The default name of the table that domain classes are associated with is derived from the domain class name so a com.demo.GuitarAmplifier domain class is associated with a GUITAR_AMPLIFIER table. The default table name could be prefixed with the plugin name. For example, if the musicEquipment plugin provides com.demo.GuitarAmplifier, the default table name could be MUSIC_EQUIPMENT_GUITAR_AMPLIFIER. If the domain class name begins with the plugin name then the additional prefix will not be added. For example, if the musicEquipment plugin provides a domain class named MusicEquipmentGuitarAmplifier then the table name would be MUSIC_EQUIPMENT_GUITAR_AMPLIFIER, not MUSIC_EQUIPMENT_MUSIC_EQUIPMENT_GUITAR_AMPLIFIER.

It may be useful to allow this behavior to be controlled with something like grails.gorm.table.prefix.enabled = true in Config.groovy. The default value for this property is false so the behavior is not enabled unless the config property is defined and assigned a value of true. In addition to that global setting we should have the ability to turn this behavior on and off on a per plugin basis. A config property named something like grails.gorm.<plugin name>.prefix.enabled may be used.

URL Mappings

URL Mappings should have explicit support for mapping requests to controllers provided by plugins. We may implement this with a new plugin token which may be used in mappings:

class UrlMappings {

	static mappings = {
        "/$plugin/$controller/$action" {
			constraints {
				// apply constraints here
			}
        }
        
        "/secadmin/$controller/$action?" {
            plugin = 'springSecurity'
			constraints {
				// apply constraints here
			}
        }
        
        "/sec/$controller/$action?"(plugin: 'springSecurity')
        
        "/manageUsers"(controller: 'user', plugin: 'springSecurity')
	}
}

This is a breaking change for any existing mappings that provide explicit support for a plugin request parameter in the mapping.