Grails Plugin Best Practices


Published on

Slides for my GR8Conf EU 2013 talk

Published in: Technology
1 Comment
  • can we've plugin descriptor with package name

    class LoggingGrailsPlugin{

    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

Grails Plugin Best Practices

  1. 1. Burt BeckwithSpringSourceGrails Plugin Best Practices
  2. 2. 2CONFIDENTIAL 2CONFIDENTIALMy Plugins Acegi App Info App Info Hibernate AspectJ (in-progress) Atomikos Binary Artifacts BlazeDS (reworked) Cache Cache-Ehcache Cache-Redis Cache-Gemfire Cloud Foundry Cloud Foundry UI Cloud Support CodeNarc Console (rework, current owner) Database Reverse Engineering Database Sessions Database Migration Database Migration JAXB Datasources Dumbster Dynamic Controller Dynamic Domain Class (core code) EJB (in-progress) EJB Glassfish (in-progress) FamFamFam Flex (reworked, current owner) Flex Scaffold (major rework, not yet released) HDIV (in-progress)
  3. 3. 3CONFIDENTIAL 3CONFIDENTIALMy Plugins Heroku Hibernate Filter (rework, current owner) Jbossas jdbc-pool (rework, current owner) JMX LazyLob Logback Memcached P6Spy UI Ratpack Remoting (reworked, current owner) SpringMVC Spring Security Core Spring Security ACL Spring Security App Info Spring Security CAS Spring Security Kerberos Spring Security LDAP Spring Security OAuth Consumer (in-progress) Spring Security OAuth Provider (in-progress) Spring Security Open ID Spring Security Shiro Spring Security UI Standalone standalone-tomcat-memcached standalone-tomcat-redis tcpmon Twitter UI Performance Webxml (rework, current owner)
  5. 5. 5CONFIDENTIAL 5CONFIDENTIALWhat Is A Plugin?A set of software components thatadds specific abilities to a largersoftware application“
  6. 6. 6CONFIDENTIAL 6CONFIDENTIALWhat Is A Plugin? Very similar to a Grails application project Plus a *GrailsPlugin.groovy plugin descriptor file Use grails create­plugin to create one Often adds artifacts, resources, scripts Good for code reuse Modular development
  7. 7. 7CONFIDENTIAL 7CONFIDENTIALApplication Directory Structure
  8. 8. 8CONFIDENTIAL 8CONFIDENTIALPlugin Directory Structure
  9. 9. 9CONFIDENTIAL 9CONFIDENTIALExtension Points The Build System Spring Application Context Dynamic method registration Auto Reloading Container Config (web.xml) Adding new Artefact Types
  10. 10. 10CONFIDENTIAL 10CONFIDENTIALTypes of Plugin New functionality• Datasources, UI Performance, etc. Wrapper• Spring Security, Searchable, Quartz, etc.• Often have many scripts, e.g. Cloud Foundry, DatabaseMigration UI Bug fix•, Resource-only• famfamfam
  11. 11. 11CONFIDENTIAL 11CONFIDENTIALPlugin Goals Convention-based approaches DRY (Dont Repeat Yourself) Required extension points satisfied Easy to distribute & install• Without additional configuration
  12. 12. 12CONFIDENTIAL 12CONFIDENTIALWhat Can A Plugin Do? Enhance classes at runtime• Add methods, constructors, properties, etc. Perform runtime Spring configuration Modify web.xml on the fly Add new controllers, tag libraries, etc.
  13. 13. 13CONFIDENTIAL 13CONFIDENTIALA Basic Pluginclass LoggingGrailsPlugin {def version = "0.1”def grailsVersion = "2.0 > *"def doWithDynamicMethods = {for (c in application.allClasses) {c.metaClass.getLog = {->LogFactory.getLog(c)}}}}
  14. 14. 14CONFIDENTIAL 14CONFIDENTIALApplication Object GrailsApplication object• available with the application variable in the plugin descriptor, andthe grailsApplication Spring bean• holds convention information• Convenient access to the config: grailsApplication.config(since the holders are deprecated)
  15. 15. 15CONFIDENTIAL 15CONFIDENTIALApplication Object Useful methods like:• get*Classes• Retrieve all GrailsClass instances for particular artifact, e.g.getControllerClasses()• add*Class(Class clazz)• Adds new GrailsClass for specified class,e.g. addControllerClass(myClass)• get*Class(String name)• Retrieves GrailsClass for given name,e.g. getControllerClass(name)
  16. 16. 16CONFIDENTIAL 16CONFIDENTIALGrails Artifacts Some resource that fulfills a convention• controllers, services, etc. Represented by the GrailsClass interface• for API of all artifacts Add new artifacts via the Artifact API•
  17. 17. 17CONFIDENTIAL 17CONFIDENTIALGrails Artifacts Design decision: include or generate artifacts?• Include is more automatic, no explicit step• Including requires workflow to override• Generate requires extra step but is more customizable• Generation is static – old files can get out of date Overriding domain classes?• Really only an issue with create-drop and update, not with migrations Overriding controllers?• “un-map” with UrlMappings"/admin/console/$action?/$id?"(controller: "console")"/console/$action?/$id?"(controller: "errors", action: "urlMapping")
  18. 18. 18CONFIDENTIAL 18CONFIDENTIALPlugin Descriptor Metadata• version, grailsVersion range, pluginExcludes, dependsOn, etc. Lifecycle ClosuresdoWithWebDescriptor← Modify XML generated for web.xml atruntimedoWithSpring ← Participate in Spring configurationdoWithApplicationContext← Post ApplicationContext initializationactivitiesdoWithDynamicMethods ← Add methods to MetaClassesonChange ← Participate in reload events
  19. 19. 19CONFIDENTIAL 19CONFIDENTIALConfiguring Springclass JcrGrailsPlugin {def version = 0.1def dependsOn = [core:0.4]def doWithSpring = {jcrRepository(RepositoryFactoryBean) {configuration = "classpath:repository.xml"homeDir = "/repo"}}}Bean name is method name,first argument is bean classSet properties on the bean
  20. 20. 20CONFIDENTIAL 20CONFIDENTIALPlanning for Customization
  21. 21. 21CONFIDENTIAL 21CONFIDENTIALPlanning for Customization Plugins are compiled first, then the app; this means thateverything can be overridden Prefer Spring beans to using new Prefer dynamic instantiation (to allow class name override) tousing new Use protected, not private and avoid static Move logic from plugin descriptor to classes
  22. 22. 22CONFIDENTIAL 22CONFIDENTIALOverriding Spring Beans Use loadAfter to define plugin load order; your beansoverride other plugins and Grails resources.groovy loads last, so applications can redefinebeans thereimport com.mycompany.myapp.MyUserDetailsServicebeans = {userDetailsService(MyUserDetailsService) {grailsApplication = ref(grailsApplication)foo = bar}}
  23. 23. 23CONFIDENTIAL 23CONFIDENTIALOverriding Spring Beans For more advanced configuration, use aBeanPostProcessor, BeanFactoryPostProcessor, ora BeanDefinitionRegistryPostProcessor
  24. 24. 24CONFIDENTIAL 24CONFIDENTIALPlugging In Dynamic Methodsdef doWithDynamicMethods = { ctx ->application.allClasses.findAll {"Codec") }.each {clz ->Object.metaClass."encodeAs${}" = {clz.newInstance().encode(delegate)}}}}Taken from Grails core, thisplugin finds all classes endingwith “Codec” and dynamicallycreates “encodeAsFoo” methods!The “delegate” variableis equivalent to “this” inregular methods
  25. 25. 25CONFIDENTIAL 25CONFIDENTIALExample Reloading Pluginclass I18nGrailsPlugin {def version = "0.4.2"def watchedResources ="file:../grails-app/i18n/*.properties"def onChange = { event ->def messageSource =event.ctx.getBean("messageSource")messageSource?.clearCache()}}Defines set of files to watchusing Spring Resource patternWhen one changes, eventis fired and plugin respondsby clearing message cache
  26. 26. 26CONFIDENTIAL 26CONFIDENTIALThe Event Object event.source• Source of the change – either a Spring Resource or ajava.lang.Class if the class was reloaded event.ctx• the Spring ApplicationContext event.application• the GrailsApplication event.manager• the GrailsPluginManager
  27. 27. 27CONFIDENTIAL 27CONFIDENTIALAdding Elements to web.xmldef doWithWebDescriptor = { xml →def contextParam = xml.context-paramcontextParam[contextParam.size() - 1] + {filter {filter-name(springSecurityFilterChain)filter-class(}}def filterMapping = xml.filter-mappingfilterMapping[filterMapping.size() - 1] + {listener {listener-class(}}}
  28. 28. 28CONFIDENTIAL 28CONFIDENTIALDependency Management Jar files• Prefer BuildConfig.groovy Maven repos→• lib directory is ok if unavailable otherwise Other plugins• def loadAfter = [controllers]• Declare dependencies in BuildConfig.groovy Grails• def grailsVersion = 2.0 > *
  29. 29. 29CONFIDENTIAL 29CONFIDENTIALBuildConfig.groovygrails.project.dependency.resolution = {inherits globallog warnrepositories {grailsCentral()flatDir name:myRepo, dirs:/path/to/repomavenLocal()mavenCentral()mavenRepo}
  30. 30. 30CONFIDENTIAL 30CONFIDENTIALBuildConfig.groovydependencies {runtime mysql:mysql-connector-java:5.1.22compile group: commons-httpclient,name: commons-httpclient,version: 3.1build(net.sf.ezmorph:ezmorph:1.0.4, net.sf.ehcache:ehcache:1.6.1) {transitive = false}test(com.h2database:h2:1.2.144) {export = false}runtime(org.liquibase:liquibase-core:2.0.1,org.hibernate:hibernate-annotations:3.4.0.GA) {excludes xml-apis, commons-logging}}
  31. 31. 31CONFIDENTIAL 31CONFIDENTIALBuildConfig.groovyplugins {runtime ":hibernate:$grailsVersion", {export = false}runtime :jquery:1.8.3compile :spring-security-core: :console:1.2build :release:2.2.1, :rest-client-builder:1.0.3, {export = false}}}
  32. 32. 32CONFIDENTIAL 32CONFIDENTIALTroubleshooting Dependency Management Run grails dependency­report• See forvisualization tips Increase BuildConfig.groovy logging level• log warn → info, verbose, or debug
  33. 33. 33CONFIDENTIAL 33CONFIDENTIALScripts Gant: Groovy + Ant - XML Can be used in apps but more common in plugins Put in the scripts directory _Install.groovy• runs when you run grails install­plugin orwhen its transitively installed - dont overwrite!might be reinstall or plugin upgrade
  34. 34. 34CONFIDENTIAL 34CONFIDENTIALScripts _Uninstall.groovy• runs when you run grails uninstall­plugin soyou can do cleanup, but dont deleteuser-created/edited stuff _Upgrade.groovy• runs when you run grails upgrade (not whenyou upgrade a plugin)
  35. 35. 35CONFIDENTIAL 35CONFIDENTIALScripts _Events.groovy• Respond tobuild events Naming convention• Prefix with _ dont show in→ grails help• Suffix with _ global script, i.e. doesnt need→to run in a project dir (e.g. create­app)eventCreateWarStart = { name, stagingDir →…}eventGenerateWebXmlEnd = {…}
  36. 36. 36CONFIDENTIAL 36CONFIDENTIALScripts Reuse existing scripts with includeTargets• includeTargets << grailsScript("_GrailsWar") Include common code in _*Common.groovy• includeTargets << new File(                 cloudFoundryPluginDir,                    "scripts/CfCommon.groovy")
  37. 37. 37CONFIDENTIAL 37CONFIDENTIALTesting Scripts Put tests in test/cli Extend grails.test.AbstractCliTestCase stdout and stderr will be in target/cli-output Run with the rest with grails test­app or alonewith grails test­app ­­other
  38. 38. 38CONFIDENTIAL 38CONFIDENTIALScriptsincludeTargets << new File(cloudFoundryPluginDir,"scripts/_CfCommon.groovy")USAGE = grails cf-list-files [path] [--appname] [--instance]target(cfListFiles: Display a directory listing) {depends cfInit// implementation of script}def someHelperMethod() { … }setDefaultTarget cfListFilesTypical Structure
  39. 39. 39CONFIDENTIAL 39CONFIDENTIALCustom Artifacts ControllerMixinArtefactHandler extendsArtefactHandlerAdapter interface ControllerMixinGrailsClass extendsInjectableGrailsClass DefaultControllerMixinGrailsClass extendsAbstractInjectableGrailsClass implementsControllerMixinGrailsClass
  40. 40. 40CONFIDENTIAL 40CONFIDENTIALCustom Artifactsclass MyGrailsPlugin {def watchedResources = [file:./grails-app/controllerMixins/**/*ControllerMixin.groovy,file:./plugins/*/grails-app/controllerMixins/**/*ControllerMixin.groovy]def artefacts = [ControllerMixinArtefactHandler]def onChange = { event →...}}
  42. 42. 42CONFIDENTIAL 42CONFIDENTIALTesting Write regular unit and integration tests in test/unitand test/integration Install using inline mechanism inBuildConfig.groovy•­plugin = ../my­plugin Test scripts as described earlier Create test apps programmatically – all of theSpring Security plugins do this with bash scripts
  43. 43. 43CONFIDENTIAL 43CONFIDENTIALTesting Use build.xml or Gradle to create single target thattests and builds plugin Use inline plugin or install from zip Zip install is deprecated, so better to use themaven-install script• Will resolve as a BuildConfig dependency (usingmavenLocal())<target name=package description=Package the plugindepends=test, doPackage, post-package-cleanup/>grails install-plugin /path/to/plugin/
  44. 44. 44CONFIDENTIAL 44CONFIDENTIALTesting Use CI, e.g. CloudBees BuildHive:••
  45. 45. 45CONFIDENTIAL 45CONFIDENTIALSource Control and Release Management
  46. 46. 46CONFIDENTIAL 46CONFIDENTIALBecoming A Plugin Developer Create an account at Discuss your idea on the User mailing list• Submit a request at• Monitor the submission for questions and comments Build (and test!) your plugin and run:• grails publish-plugin --stacktrace Profit!
  47. 47. 47CONFIDENTIAL 47CONFIDENTIALSource Control and Release Management Internal server for jars & plugins is easy with Artifactory orNexusrepositories {grailsPlugins()grailsHome()mavenRepo "http://yourserver:8081/artifactory/libs-releases-local/"mavenRepo "http://yourserver:8081/artifactory/plugins-releases-local/"grailsCentral()}
  48. 48. 48CONFIDENTIAL 48CONFIDENTIALSource Control and Release Management Release with grails publish­plugin ­­repository=repo_namegrails.project.dependency.distribution = {remoteRepository(id: "repo_name",url: "http://yourserver:8081/artifactory/plugins-snapshots-local/") {authentication username: "admin", password: "password"}}
  49. 49. 49CONFIDENTIAL 49CONFIDENTIALSource Control and Release Management Use “release” plugin• Sends tweet from @grailsplugins and email to plugin forum• Will not check code into SVN for youplugins {build :release:2.2.1, :rest-client-builder:1.0.3, {export = false}}
  50. 50. 50CONFIDENTIAL 50CONFIDENTIALBest Practices and Tips
  51. 51. 51CONFIDENTIAL 51CONFIDENTIALBest Practices and Tips Delete anything auto-generated that you dont use• grails-app/views/error.gsp• Empty grails-app/i18n/• _Uninstall.groovy and other generated scripts• web-app files• grails-app/conf/UrlMappings.groovy• Empty descriptor callbacks
  52. 52. 52CONFIDENTIAL 52CONFIDENTIALBest Practices and Tips Exclude files used in development or testing• Use def pluginExcludes = [...]• src/docs• Test artifacts Create .gitignore (manually or withintegrate-with –git) Open every file, decide that you own it or not
  53. 53. 53CONFIDENTIAL 53CONFIDENTIALBest Practices and Tips Use dependency management inBuildConfig.groovy, not lib dir Change the minimum Grails version to 2.0 or the lowestversion youre comfortable supporting:• def grailsVersion = 2.0 > *
  54. 54. 54CONFIDENTIAL 54CONFIDENTIALBest Practices and Tips Write tests Run them often Run tests before commit and especially beforerelease (use build.xml or Gradle) Create test apps (ideally programmatically) Programmatically build inline plugin location:def customerName = System.getProperty("")grails.plugin.location."$customerName" = "customer-plugins/$customerName"
  55. 55. 55CONFIDENTIAL 55CONFIDENTIALBest Practices and Tips Prefer Java to Groovy (or @CompileStatic) forperformance if applicable Instead of using inline plugins, install into a testapp and use Meld or another diff tool to keep insync Before you release, run package-plugin and openthe ZIP, look at whats there
  56. 56. 56CONFIDENTIAL 56CONFIDENTIALBest Practices and Tips Use a sensible naming convention, e.g.grails.plugin.<pluginname> Most plugin metadata is not optional:• Fix grailsVersion• Update description and documentation• Set value for license, issueManagement, scm (andorganization, developers if applicable)
  57. 57. 57CONFIDENTIAL 57CONFIDENTIALBest Practices and Tips Get to 1.0• Initial version 0.1 is just a suggestion• People trust 1.0+ more Start using source control early• Easy to run git init locally and later github→ Support your plugin!
  58. 58. 58CONFIDENTIAL 58CONFIDENTIALBest Practices and Tips
  59. 59. 59CONFIDENTIAL 59CONFIDENTIALWant To Learn More?