Migrando de Grails 2.5.5 a Grails 3.2.4

Para evitar seguir acumulando deuda técnica decidimos que ya era el momento para migrar nuestro sistema ADMIN – Administración de Mantenimientos a la última versión disponible de Grails (3.2.4).

El cambio interno y externo de Grails 2 a Grails 3 ha sido grande, modificando incluso la estructura de la aplicación. En Grails 3.2.4 destacan las siguientes tecnologías:

  • Spring 4.3.1 (MVC, seguridad)
  • Hibernate 5.1.1 (por debajo de GORM 6)
  • Spring Boot 1.4.0
  • Gradle 3.0

Vamos a compartir los pasos que hemos seguido durante este proceso.

Seguir los pasos de la documentación

Detalla muy bien la nueva estructura de la aplicación, ayudando a mover todos los archivos. (Grails guide chapter Upgrading)

Resolver problemas de memoria

Inicialmente no podíamos ni compilar la aplicación, necesitamos otorgar más recursos tanto a la propia aplicación como al demonio de Gradle, para ello en el build.gradle se puede añadir al final:

tasks.withType(GroovyCompile) {
configure(groovyOptions.forkOptions) {
memoryMaximumSize = '1g'
jvmArgs = ['-XX:MaxPermSize=512m', '-Xms512m', '-Xmx1g']
}
}

Fuente: Gradle groovy compile and out of memory error

Logs

Ha pasado de utilizar Log4j a Logback. El único cambio que hemos realizado ha sido añadir una línea al archivo de configuración …/grails-app/conf/logback.groovy dentro del bloque definido para el entorno de desarrollo:

logger("com.jpicadoyasociados", INFO)

Spring Security Core Plugin

Para seguir utilizando el mismo esquema para el control de acceso y la administración de la seguridad, hay que realizar algunos cambios detallados en su guía (Spring Security Core Plugin – Reference Documentation)

  • Cambiar los nombres de los campos en el formulario de login y debe llamar a /login/authenticate.
  • Si se está utilizando interceptUrlMap ahora se debe definir como un array de arrays:
grails.plugin.springsecurity.interceptUrlMap = [
[pattern: '/secure/someAction', access: ["hasRole('ROLE_ADMIN')"]],
[pattern: '/secure/someOtherAction', access: ["authentication.name == 'ralph'"]]
]

Controles AJAX

Ya no están incluidos por defecto en Grails (formRemote, submitToRemote, etc.). Se pueden añadir como un plugin. Se decidió deprecarlos porque no siguen las mejores prácticas (añaden javaScript inline en el HTML).

Para tenerlos de nuevo disponibles basta con añadir la siguiente dependencia:

compile 'org.grails.plugins:ajax-tags:1.0.0'

Fuente: Grails 3.0 Ajax call replacement due to deprecation

Scaffolding

Se ha modificado la declaración en los controladores de que se debe generar dinámicamente el CRUD de la clase de dominio, ahora utiliza el siguiente formato:

class UbicacionRegistroMovimientoController {
 static scaffold = UbicacionRegistroMovimiento

Ahora el scaffolding de Grails utiliza el Fields plugin, y no genera en las vistas el código para cada uno de los atributos de la clase de dominio (Fields se encarga de ello).

Fields plugin

Queremos utilizar Bootstrap en las pantallas, manteniendo los estilos ya definidos y utilizados en la aplicación, para es necesario modificar las plantillas del plugin.

Puede definirse la plantilla ../grails-app/views/templates/_fields/_table.gsp para modificar la tabla con el listado de objetos que se genera en:

<f:table collection="\${${propertyName}List}" />

Puede definirse la plantilla ../grails-app/views/templates/_fields/_list.gsp para modificar como se visualiza el listado de atributos generado por:

<f:display bean="${propertyName}" />

Para modificar como se muestra cada atributo (tanto en edición como en visualización), aquí hay unas instrucciones: Customizing Field Rendering – Reference Documentation.

Un ejemplo, para definir como debe trabajarse en las pantallas con campos de tipo fecha:

Utilizamos el wrapper default definido en ../grails-app/views/_fields/default/_wrapper.gsp

<%@ page defaultCodec="html" %>
<div class="form-group">
<label for="${property}" class="col-sm-2 control-label ${(required)?'text-danger':''}">
${label}
<g:if test="${required}">
<span class="required-indicator danger">*</span>
</g:if>
</label>
<div class="col-sm-10">
<%= widget %></div>
</div>

Y el displayWidget definido para fechas en ../grails-app/views/_fields/date/_displayWidget.gsp

<g:if test="${value == null || value == ''}">
--
</g:if>
<g:else>
<g:if test="${constraints.attributes.precision=='day'}">
<g:formatDate date="${value}" format="yyyy-MM-dd" />
</g:if>
<g:elseif test="${constraints.attributes.precision=='minute'}">
<g:formatDate date="${value}" format="yyyy-MM-dd HH:mm:ss a" />
</g:elseif>
<g:else>
<g:formatDate date="${value}" format="yyyy-MM-dd HH:mm:ss a" />
</g:else>
</g:else>

Y el widget definido también para fechas en ../grails-app/views/_fields/date/_widget.gsp

<div class="datePickerForm">
<g:if test="${constraints.nullable == true}">
<g:datePicker name="${property}" value="${value}" precision="${constraints.attributes.precision}" relativeYears="${constraints.attributes.relativeYears}" default="none" noSelection="[null: '']" />
</g:if>
<g:else>
<% if(value==null){value = new Date()} %>
<g:datePicker name="${property}" value="${value}" precision="${constraints.attributes.precision}" relativeYears="${constraints.attributes.relativeYears}"/>
</g:else></div>

De esta forma el código generado durante el scaffolding muestra las fechas de acuerdo a su precisión. Y si se realiza la llamada manual, se puede hacer así:

Para mostrar:

<f:display bean="obj" property="fecha" />

Para editar:

<f:field bean="obj" property="fecha" />

De esta forma, hemos definido plantillas para el tipo de dato string (cambiando el tipo de control según la longitud máxima permitida o los atributos de la propiedad), intfloat (utilizando el locale de la aplicación, evitando la escritura de otros caracteres mediante javaScript, y añadiendo un teclado virtual), boolean y como se deben mostrar cada una de las relaciones (oneToOne, oneToMany, manyToOne y manyToMany).

Jasper

De los principales plugins sólo falta por estar disponible el de Jasper (lo utilizamos para generar los .pdf de las órdenes de servicio). No es un gran problema, es muy sencillo realizar las llamadas directamente añadiendo Jasper como dependencia, e imitando el código del plugin de la versión 2 para no tener que modificar nada más.

Estas son las dependicias a declarar:

compile group: 'com.lowagie', name: 'itext', version: '2.1.7'
compile group: 'net.sf.jasperreports', name: 'jasperreports', version: '6.4.0'
compile 'net.sf.jasperreports:jasperreports-fonts:6.0.0'

Bonus

  • Control de sessiones concurrentes: si tenéis un concurrency filter en grails 2, puede que tengáis que modificar su funcionamiento. En este enlace explican como controlar que cada usuario sólo tenga 1 sesión a la vez en el sistema.
  • Spring-security-ui: si utilizaís este plugin para la administración de usuarios, perfiles y demás parámetros de la seguridad, sus clases han cambiado, por lo que puede que tengáis que modificar algo el código si habéis personalizado o extendido sus elementos.

Conclusión

Una vez actualizada y funcionando la aplicación podemos empezar a  realizar revisiones iterativas buscando implementar las optimizaciones que el avance en las tecnologías nos permiten.

Lo que por ahora ha supuesto un paso atrás para nosotros ha sido como funciona el Scaffolding. En la versión 2 utilizamos plantaillas propias para generar todos los controladores y vistas, y luego a partir de ese código sin errores realizabamos mejoras. El plugin en la versión 3, aún pudiendo personalizar la salida como ya hemos visto no genera todo el código como antes, de forma que cuando queremos modificar una vista tenemos que escribir mucho más código.