Composer version, setup_version y la versión del módulo en Magento2

Hace ya unas varias semanas me topé con una serie de cuestiones relativas a procesos de deploy (nada mejor que aprender con casos de la vida real).

Revisando un poco el problema y leyendo sobre distintas opciones, alternativas y demás yerbas; terminé volviendo sobre todo el proceso de desarrollo (varios posts ya en draft).

Esto me llevó a repasar lo que se conversó en la meetup #3 de MugAr con respecto a la forma de escribir módulos. El próximo paso fue plantear algunas dudas y escenarios dentro del canal de Slack.

Finalmente, en la meetup #5 de MugAr se reabrió el tema y se cambió la definición. La misma está disponible en Cómo escribimos módulos (allí siempre estará la versión definitiva).

Volviendo al problema en cuestión: ¿cuál es la forma más apropiada de manejar la versión de un módulo? (intencional he usado la palabra «apropiada» y no «correcta»).

Si nos fijamos en el código de un módulo cualquiera, Magento sólo nos permite definir la versión de algo aquí:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Barbanet_SampleModule" setup_version="2.24.0">
        <sequence>
            <module name="Magento_Store" />
            <module name="Magento_Theme" />
        </sequence>
    </module>
</config>

¿Pero qué es el setup_version?.

Según la documentación (y lo que podemos inferir del nombre de la propiedad, claro), setup_version es la versión del esquema de base de datos de nuestro módulo.

Ok, ¿pero que pasa si no presto atención a esa definición e igualmente uso el setup_version para definir la versión del módulo?.

Para poder responder a esa pregunta, primero será necesario recordar que una vez que activamos un módulo, Magento nos muestra este mensaje:

The following modules have been enabled:
- Barbanet_SampleModule

To make sure that the enabled modules are properly registered, run 'setup:upgrade'.
Cache cleared successfully.
Generated classes cleared successfully. Please run the 'setup:di:compile' command to generate classes.
Info: Some modules might require static view files to be cleared. To do this, run 'module:enable' with the --clear-static-content option to clear them.

Esto quiere decir que luego de bajar el código (vía repositorio o composer) deberemos también ejecutar un comando que hará operaciones sobre la base de datos.

La primera vez que instalamos un módulo podríamos tolerarlo, ¿pero cada vez que hagamos un cambio al módulo también?

¿Y el famoso deploy con zero-downtime?

Me ha pasado (a la mayoría) tener que deployar cambios en un módulo que sólo eran de código, incluso a veces sólo traducciones. ¿No suena familiar el tema?

En teoría, si he modificado o arreglado algo del módulo, basándome en el versionado semántico, la versión de mi módulo debería incrementarse.

Al usar setup_version para llevar el versionado del módulo, incluso cuando sólo fuese una traducción o código que no afectara la base de datos, deberé ejecutar el comando:

bin/magento setup:upgrade

Por este motivo el uso de setup_version es un potencial problema. Nos obligará siempre a cerrar la tienda para hacer un deploy ya que estaremos operando contra la base de datos.

Vayamos entonces a la segunda alternativa para definir y controlar la versión de un módulo en Magento2. Usar la propiedad version dentro de Composer.

En la propia documentación de Magento (al momento del post la versión estable es 2.2.5) se mencionan una serie de propiedades a usar en nuestro composer.json.

El ejemplo allí mostrado es:

{
    "name": "your-name/module-Acme",
    "description": "Test component for Magento 2",
    "require": {
        "php": "~5.5.0|~5.6.0",
        "magento/module-store": "1.0.0-beta",
        "magento/module-catalog": "1.0.0-beta",
        "magento/module-catalog-inventory": "1.0.0-beta",
        "magento/module-ui": "self.version",
        "magento/magento-composer-installer": "*"
    },
    "suggest": {
      "magento/module-webapi": "1.0.0-beta"
    },
    "type": "magento2-module",
    "version": "1.0.0-beta",
    "license": [
        "OSL-3.0",
        "AFL-3.0"
    ],
    "autoload": {
        "files": [ "registration.php" ],
        "psr-4": {
            "Magento\\CatalogImportExport\\": ""
        }
    }
}

Ahora si, ¡está resuelto!. ¿Está resuelto?. ¿No?.

Según el propio Composer el uso de la propiedad version debería ser una excepción ya que por defecto se usará el tag del repositorio para determinar la versión.

Ok, entonces, si logramos controlar nuestro costado neurótico, en este punto estaríamos haciendo esto con nuestros módulos en Magento2:

  • setup_version se usará sólo para cuando el módulo deba modificar estructura o insertar o cambiar algo dentro de la base de datos.
  • la propiedad version dentro de composer.json no deberá usarse como regla por defecto.

Esto último incluso será puesto en práctica por Magento en la versión 2.3.0, como se anunciara en el Imagine de este año.

En un mundo ideal sería muy fácil si con eso hubiéramos resuelto nuestra duda y nuestro problema. Pero el tema no termina acá.

Tomemos el módulo Magento_TaxImportExport como ejemplo (no es el único).

Si miramos el archivo app/code/Magento/TaxImportExport/etc/module.xml veremos que se hace uso del setup_version, pero el módulo no tiene siquiera el directorio Setup.

Ahora, miremos el archivo app/code/Magento/TaxImportExport/composer.json, allí el valor de la propiedad version no tiene nada que ver con el valor de setup_version.

Recapitulando:

  • setup_version y composer version no tienen nada que ver entre si.
  • setup_version no puede no estar definido (en 2.3 si, pero esperemos a que se publique)
  • al menos en la instalación Magento nos sugiere ejecutar bin/magento setup:upgrade para registrar correctamente un nuevo módulo.
  • composer version no debería usarse según el propio Composer.
  • la versión del módulo sería determinada por el tag en el repositorio (y sólo cuando no fuera posible, se usará la propiedad version, la cual será mantenida de forma manual).

¿Ahora si tenemos una definición completa?

Tampoco.

Si hubiéramos seguido esas definiciones al pie de la letra, tendríamos un nuevo problema del cual ocuparnos. ¿Cómo se qué versión del módulo tengo sin tener que recurrir al código y/o repositorio?

Quizás la primera respuesta sea que no es algo relevante. Y como primera respuesta puede entenderse, pero no es útil ya que, mayormente, quienes desarrollen y vendan módulos necesitan esa información, por ejemplo, para proveer soporte.

No sólo eso, en infinidad de oportunidades hemos visto o hemos tenido que hacer customizaciones en un módulo propio que considere la versión de tal módulo para aplicar una solución u otra.

En estos dos escenarios, tener disponible la versión del módulo es más que útil.

Aquí, posiblemente, se pueda trazar la primera línea. Estarán aquellos a quienes la versión de un módulo pueda importarles poco y nada; mientras que para otros será una información valiosa.

Los módulos en Magento2 no nos permiten llevar control de la versión en el código. ¿Es quizás un error de diseño o es una propiedad carente de valor?.

¿Qué opciones podríamos implementar para terminar de definir cómo disponer de la versión de un módulo para luego actuar en consecuencia?.

¿Quizás la solución sea que el módulo X, que depende de Y, contemple cada modificación y en base a la modificación de Y haya una versión de X? (y evitar que un módulo de terceros sea capaz de manejar diferentes versiones de otro módulo de terceros)

Si lo consideramos una falla de diseño, ¿tiene sentido aceptar el uso de un Helper para definir la versión de un módulo?.

En lo personal aún no llego a una conclusión, aunque las reglas que han encontrado argumento práctico y teórico (y logran convivir también con la neurosis y los casos de la vida real) son:

Dicho esto. ¿cuál sería entonces la mejor forma llevar la versión de un módulo para que, además de respetar las definiciones de Magento y Composer, podamos hacer uso de ese valor para tomar otro tipo de decisiones en tiempo de ejecución?