¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.dev
Daniel Peña 11/11/2024 Cargando comentarios…
OpenRewrite es un software que plantea un enfoque para el mantenimiento y “modernización” (actualización) de proyectos.
Es decir:
¿Cómo lo hace?
Se basa en el concepto receta (recipe), como una receta de cocina, que contiene una serie de pasos a seguir.
Por ejemplo, si trabajamos con Spring security 5.4 y queremos pasar a la versión 5.5, iríamos a la página oficial donde encontraríamos un changelog y unas guías de migración.
En este caso, pongamos que queremos hacer lo siguiente:
El concepto receta integraría toda la información necesaria para realizar esos pasos, de manera que solo tenemos que preocuparnos de pasar esa receta a nuestro proyecto. La receta se encargaría de hacer todos esos pasos por nosotros.
¿Quién lo hace?
Se llama OpenRewrite porque es un proyecto open source donde los desarrolladores/as de frameworks, libs… se dedican a escribir las recetas que necesitarán las migraciones de las herramientas que han desarrollado.
El propio core de OpenRewrite escribe recetas para migraciones comunes que no pertenecen a ningún framework.
Las podemos encontrar aquí:
En la propia página de OpenRewrite hay una sección que nos ofrece las recetas más comunes y que suelen ser las mas buscadas.
No solo podemos nutrirnos de las recetas que escriben otros, sino que también podemos escribir nuestras propias recetas. Si necesitamos unos pasos específicos para actualizar nuestros proyectos, podemos escribir nuestras recetas y ejecutarlas (por ejemplo, cambiamos objetos en libs, invocaciones...).
El core de OpenRewrite se basa en la ejecución de un maven-plugin. Por lo tanto, podemos hacer uso de él de la misma forma en que ejecutamos un maven-plugin.
La gente de Moderne.io dispone de un SaaS que ofrece lo mismo que el core, pero de una manera masiva. Esto es justo lo que nos interesa: pasar recetas a tantos repositorios como tengamos para que estén siempre actualizados.
El producto SaaS es de pago y su configuración es básica. Le damos credenciales para acceder a nuestros repositorios GIT y él se encargará de tener actualizado y pasar las recetas que indiquemos de manera autónoma. Ofrece estadísticas y un sinfín de cosas más.
Tradicionalmente, se utilizaba Java AST (Abstract Syntax Tree) para la manipulación y creación de nodos que conformaban un archivo Java.
Con él éramos capaces de hacer transformaciones aisladas, es decir, no teníamos contexto de si nuestro archivo Java tenía sentido o relación con otros elementos dentro de nuestro proyecto.
OpenRewrite se basa en LostLees Semantic Trees. Este enfoque no solo es capaz de transformar nuestro código Java, sino que es capaz de interpretar el contexto y las relaciones entre los distintos elementos de nuestro proyecto. Entiende la semántica del código que analiza.
LST monta el árbol de relaciones de bloques de código montando una estructura de relaciones. Así, vemos no solo que lee el código y sabe qué es una variable y dónde está escrita, sino que también sabe dónde se está usando y referenciando. Lo mismo con los métodos, constructores y demás elementos del código:
A través de varios ejemplos sencillos que puedes ir siguiendo en tu ordenador, vamos a ir probando algunas recetas públicas y vamos a escribir algunas propias.
Todo el código de esta demo lo puedes encontrar en este repositorio.
git clone https://github.com/paradigmadigital/openrewrite-tutorial
git checkout demo1
En la branch demo1 encontraremos dos carpetas principales:
La carpeta recipes contendrá nuestras recetas. La carpeta singleprojectstarter es un proyecto hecho son SpringInitalizer en su versión Spring boot 2.7.9 y Java 11.
Rewrite-maven
Como he comentado antes, hay muchos “proveedores” de recetas. En los siguientes ejemplos vamos a usar recetas del rewrite-maven. Estas recetas están enfocadas únicamente en modificaciones de ficheros pom.
Si nos fijamos, el parent que trae es el de Spring Boot (2.7.9 y Java 11). Ahora, vamos a buscar una receta que sea capaz de cambiarnos el parent en las recetas disponibles en el repo oficial de OpenRewrite.
Entre las recetas precocinadas de Maven vemos que esta cambia el parent.
Esta receta es parametrizada, así que editamos el archivo recipes.yml con este contenido en la carpeta “recipes” del repo.
Así que escribimos la primera receta:
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.Recipe1
displayName: Some recipe examples
recipeList:
- org.openrewrite.maven.ChangeParentPom:
oldGroupId: org.springframework.boot
newGroupId: org.springframework.boot
oldArtifactId: spring-boot-starter-parent
newArtifactId: spring-boot-starter-parent
newVersion: 2.7.13
Hay parámetros generales que debemos especificar para el recetario que estamos creando:
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.Recipe1
displayName: Some recipe examples
recipeList:
RecipeList
En este caso, nuestro recetario tiene una única receta de tipo org.openrewrite.maven.ChangeParentPom, que es una receta parametrizada:
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.Recipe1
displayName: Some recipe examples
recipeList:
- org.openrewrite.maven.ChangeParentPom:
oldGroupId: org.springframework.boot
newGroupId: org.springframework.boot
oldArtifactId: spring-boot-starter-parent
newArtifactId: spring-boot-starter-parent
newVersion: 2.7.13
Ejecutando la receta sobre el proyecto
Como indicamos al principio, vamos a usar el rewrite-maven-plugin y, por lo tanto, necesitamos estar en el directorio del proyecto donde vamos a ejecutar el plugin.
cd singleprojectstarter
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite:rewrite-maven:8.1.2 \
-Drewrite.activeRecipes=com.openrewrite.demo.Recipe1 \
-Drewrite.configLocation=../recipes/recipes.yaml
Argumentos:
🚨Warning “proyectos multimódulo”:
Cuando nuestro proyecto es multimódulo, es necesario que la ruta del configLocation sea absoluta, ya que se ejecutará la receta para el módulo Maven padre y todos sus módulos hijos. Si la ruta fuera relativa, cuando entrara la ejecución del plugin en el módulo hijo, no cuadraría. Poniendo la ruta absoluta nos quitamos de problemas.
El final de la ejecución nos muestra un resumen de las recetas ejecutadas y los cambios:
...
[INFO] Validating active recipes...
[INFO] Project [singleprojectstarter] Resolving Poms...
[INFO] Project [singleprojectstarter] Parsing source files
[INFO] Running recipe(s)...
[WARNING] Changes have been made to singleprojectstarter/pom.xml by:
[WARNING] com.openrewrite.demo.Recipe1
[WARNING] org.openrewrite.maven.ChangeParentPom: {oldGroupId=org.springframework.boot, newGroupId=org.springframework.boot, oldArtifactId=spring-boot-starter-parent, newArtifactId=spring-boot-starter-parent, newVersion=2.7.13}
[WARNING] Please review and commit the results.
[WARNING] Estimate time saved: 5m
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.911 s
[INFO] Finished at: 2024-05-30T12:39:43+02:00
...
Podemos ver el diff y los cambios que ha realizado:
--- a/singleprojectstarter/pom.xml
+++ b/singleprojectstarter/pom.xml
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
- <version>2.7.9</version>
+ <version>2.7.13</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
Es poco, pero ya hemos ejecutado una receta parametrizada.
Hasta ahora, hemos visto que:
Ahora, le voy a decir que me suba a la última versión del Spring Boot a la versión 3.
En el ejemplo anterior también hemos subido de versión, pero la receta era ChangeParentPom. Al no cambiar el groupId ni el artifactId no hemos cambiado el parent, sino que lo hemos actualizado.
Para ello, tenemos [recetas precompiladas del rewrite-maven-plugin]. Esta receta es más correcta para este propósito y, si nos fijamos en su parametrización, no deja cambiar el groupId ni el artifactId. Solo tiene 3 parámetros que indican que, si el parent del proyecto tiene ese groupId y ese artifactId, debe migrarlo a la nueva versión 3.1.0.
groupId: org.springframework.boot
artifactId: spring-boot-starter-parent
newVersion: 3.1.0
Así que añadimos otro paso a nuestra receta com.openrewrite.demo.Recipe1, para ejecutar org.openrewrite.maven.UpgradeParentVersion.
Indicamos la versión que queremos subir y nos quedará así:
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.Recipe1
displayName: Change Maven Parent Pom
recipeList:
- org.openrewrite.maven.ChangeParentPom:
oldGroupId: org.springframework.boot
newGroupId: org.springframework.boot
oldArtifactId: spring-boot-starter-parent
newArtifactId: spring-boot-starter-parent
newVersion: 2.7.13
- org.openrewrite.maven.UpgradeParentVersion:
groupId: org.springframework.boot
artifactId: spring-boot-starter-parent
newVersion: 3.1.0
Reiniciamos los cambios (git stash) y vamos a ver que se ejecutan las recetas en orden:
git stash
Como no ha cambiado el nombre de la receta, volvemos a ejecutar lo mismo:
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite:rewrite-maven:8.1.2 \
-Drewrite.activeRecipes=com.openrewrite.demo.Recipe1 \
-Drewrite.configLocation=../recipes/recipes.yaml
...
[INFO] Validating active recipes...
[INFO] Project [singleprojectstarter] Resolving Poms...
[INFO] Project [singleprojectstarter] Parsing source files
[INFO] Running recipe(s)...
[WARNING] Changes have been made to singleprojectstarter/pom.xml by:
[WARNING] com.openrewrite.demo.Recipe1
[WARNING] org.openrewrite.maven.ChangeParentPom: {oldGroupId=org.springframework.boot, newGroupId=org.springframework.boot, oldArtifactId=spring-boot-starter-parent, newArtifactId=spring-boot-starter-parent, newVersion=2.7.13}
[WARNING] org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=3.1.0}
[WARNING] Please review and commit the results.
[WARNING] Estimate time saved: 5m
Vemos que ha ejecutado la recipeList en orden. Primero subió a la 2.7.9 y, en el siguiente paso, a la 3.1.0.
Si vemos el diff, ha subido la versión (ahora tenemos la 3.1.0), que era la foto final que queríamos:
--- a/singleprojectstarter/pom.xml
+++ b/singleprojectstarter/pom.xml
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
- <version>2.7.9</version>
+ <version>3.1.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
Hacemos git stash para volver a la foto inicial.
Encontramos otra receta de actualización o añadido de property. Vamos a aprovecharla para subir a Java 17.
En este caso, no queremos que esté asociada a la receta del com.openrewrite.demo.ChangeParentPom. Queremos tenerla en el recetario, pero separada para poder ejecutarla aisladamente.
Así que, en el mismo fichero, añadimos “—” para indicar que son parámetros de otra receta e insertamos nuestros datos.
El fichero final quedará así:
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.Recipe1
displayName: Change Maven Parent Pom
recipeList:
- org.openrewrite.maven.ChangeParentPom:
oldGroupId: org.springframework.boot
newGroupId: org.springframework.boot
oldArtifactId: spring-boot-starter-parent
newArtifactId: spring-boot-starter-parent
newVersion: 2.7.13
- org.openrewrite.maven.UpgradeParentVersion:
groupId: org.springframework.boot
artifactId: spring-boot-starter-parent
newVersion: 3.1.0
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.Recipe2
displayName: Update java property to 17
recipeList:
- org.openrewrite.maven.AddProperty:
key: java.version
value: 17
preserveExistingValue: false
trustParent: false
Ahora vamos a ejecutar la receta com.openrewrite.demo.UpdateJava17, que vemos que hace uso de la receta interna org.openrewrite.maven.AddProperty:
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite:rewrite-maven:8.1.2 \
-Drewrite.activeRecipes=com.openrewrite.demo.Recipe2 \
-Drewrite.configLocation=../recipes/recipes.yaml
[INFO] Validating active recipes...
[INFO] Project [singleprojectstarter] Resolving Poms...
[INFO] Project [singleprojectstarter] Parsing source files
[INFO] Running recipe(s)...
[WARNING] Changes have been made to singleprojectstarter/pom.xml by:
[WARNING] com.openrewrite.demo.Recipe2
[WARNING] org.openrewrite.maven.AddProperty: {key=java.version, value=17, preserveExistingValue=false, trustParent=false}
[WARNING] Please review and commit the results.
[WARNING] Estimate time saved: 5m
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
Vemos en el diff que se ha actualizado.
@@ -14,7 +14,7 @@
<name>singleprojectstarter</name>
<description>Demo project for Spring Boot</description>
<properties>
- <java.version>11</java.version>
+ <java.version>17</java.version>
</properties>
<dependencies>
Vamos a darle una naturaleza web añadiendo el spring-boot-starter-web.
Encontramos la receta y, como esta receta tiene entidad propia, creamos una definición con el nombre com.openrewrite.demo.AddWebNature en nuestro fichero de configuración.
Añadimos:
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.AddWebNature
displayName: Add Web nature
recipeList:
- org.openrewrite.maven.AddDependency:
groupId: org.springframework.boot
artifactId: spring-boot-starter-web
version: 3.1.0
onlyIfUsing: org.springframework.boot.*
scope: compile
acceptTransitive: true
💡“El árbol LST es muy listo”
A veces queremos tener la dependencia pero no la usamos en código. En este caso, le estoy “engañando” indicando que sí que tengo un import de org.springframework.boot en la clase Application.java. Al montar el árbol, OpenRewrite verá que sí se está usando ese import y, entonces, sí que ejecutará la receta añadiendo la dependencia.
Ejecutamos la receta com.openrewrite.demo.AddWebNature:
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite:rewrite-maven:8.1.2 \
-Drewrite.activeRecipes=com.openrewrite.demo.AddWebNature \
-Drewrite.configLocation=../recipes/recipes.yaml
[INFO] Running recipe(s)...
[WARNING] Changes have been made to singleprojectstarter/pom.xml by:
[WARNING] com.openrewrite.demo.AddWebNature
[WARNING] org.openrewrite.maven.AddDependency: {groupId=org.springframework.boot, artifactId=spring-boot-starter-web, version=3.1.0, scope=compile, onlyIfUsing=org.springframework.boot.*, acceptTransitive=true}
[WARNING] Please review and commit the results.
[WARNING] Estimate time saved: 5m
Vemos el diff y vemos que ha metido la dependencia.
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
<dependency>
Imaginemos que tenemos que retroceder una versión de una lib determinada porque tiene un bug. Vamos a llamarla WebErrorWorkAround.
En nuestra intervención, tenemos que hacer estos pasos:
Usamos el step de borrar y el de añadir, en ese orden. La receta nos quedará así (la añadimos al recetario):
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.WebErrorWorkAround
displayName: Workaround for Web error on latest version
recipeList:
- org.openrewrite.maven.RemoveDependency:
groupId: org.springframework.boot
artifactId: spring-boot-starter-web
scope: compile
- org.openrewrite.maven.AddDependency:
groupId: org.springframework.boot
artifactId: spring-boot-starter-web
version: 3.1.1
scope: runtime
onlyIfUsing: org.springframework.boot.test.context.*
type: jar
classifier: ''
optional: null
acceptTransitive: false
- org.openrewrite.maven.AddCommentToMavenDependency:
xPath: /project/dependencies/dependency
groupId: org.springframework.boot
artifactId: spring-boot-starter-web
commentText: This is excluded due to CVE <X> and will be removed when we upgrade the next version is available.
Como podemos ver, quitamos el starter web, añadimos el starter web en otra versión y añadimos un comentario al pom.xml indicando por qué se ha realizado este cambio usando la receta pública org.openrewrite.maven.AddCommentToMavenDependency.
Ejecutamos la receta com.openrewrite.demo.WebErrorWorkAround:
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite:rewrite-maven:8.1.2 \
-Drewrite.activeRecipes=com.openrewrite.demo.WebErrorWorkAround \
-Drewrite.configLocation=../recipes/recipes.yaml
[INFO] Running recipe(s)...
[WARNING] Changes have been made to singleprojectstarter/pom.xml by:
[WARNING] com.openrewrite.demo.WebErrorWorkAround
[WARNING] org.openrewrite.maven.RemoveDependency: {groupId=org.springframework.boot, artifactId=spring-boot-starter-web, scope=compile}
[WARNING] org.openrewrite.maven.AddDependency: {groupId=org.springframework.boot, artifactId=spring-boot-starter-web, version=3.1.1, scope=runtime, onlyIfUsing=org.springframework.boot.test.context.*, type=jar, classifier=, acceptTransitive=false}
[WARNING] org.openrewrite.maven.AddCommentToMavenDependency: {xPath=/project/dependencies/dependency, groupId=org.springframework.boot, artifactId=spring-boot-starter-web, commentText=This is excluded due to CVE <X> and will be removed when we upgrade the next version is available.}
[WARNING] Please review and commit the results.
[WARNING] Estimate time saved: 5m
Vemos el diff y encontramos la nueva versión del springboot-starter-web y el comentario añadido.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
+ <dependency>
+ <!--This is excluded due to CVE <X> and will be removed when we upgrade the next version is available.-->
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <version>3.1.1</version>
+ <classifier></classifier>
+ <scope>runtime</scope>
+ </dependency>
El contenido total de nuestro recipes.yaml es este:
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.Recipe1
displayName: Change Maven Parent Pom
recipeList:
- org.openrewrite.maven.ChangeParentPom:
oldGroupId: org.springframework.boot
newGroupId: org.springframework.boot
oldArtifactId: spring-boot-starter-parent
newArtifactId: spring-boot-starter-parent
newVersion: 2.7.13
- org.openrewrite.maven.UpgradeParentVersion:
groupId: org.springframework.boot
artifactId: spring-boot-starter-parent
newVersion: 3.1.0
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.Recipe2
displayName: Update java property to 17
recipeList:
- org.openrewrite.maven.AddProperty:
key: java.version
value: 17
preserveExistingValue: false
trustParent: false
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.AddWebNature
displayName: Add Web nature
recipeList:
- org.openrewrite.maven.AddDependency:
groupId: org.springframework.boot
artifactId: spring-boot-starter-web
version: 3.1.0
onlyIfUsing: org.springframework.boot.*
scope: compile
acceptTransitive: true
type: specs.openrewrite.org/v1beta/recipe
name: com.openrewrite.demo.WebErrorWorkAround
displayName: Workaround for Web error on latest version
recipeList:
- org.openrewrite.maven.RemoveDependency:
groupId: org.springframework.boot
artifactId: spring-boot-starter-web
scope: compile
- org.openrewrite.maven.AddDependency:
groupId: org.springframework.boot
artifactId: spring-boot-starter-web
version: 3.1.1
scope: runtime
onlyIfUsing: org.springframework.boot.test.context.*
type: jar
classifier: ''
optional: null
acceptTransitive: false
- org.openrewrite.maven.AddCommentToMavenDependency:
xPath: /project/dependencies/dependency
groupId: org.springframework.boot
artifactId: spring-boot-starter-web
commentText: This is excluded due to CVE <X> and will be removed when we upgrade the next version is available.
Hasta aquí hemos visto cómo podemos ir ejecutando recetas en un proyecto Maven y también algunas de las recetas comunes, cómo configurarlas y cómo ejecutarlas. En el siguiente post vamos a ver recetas específicas de Spring Framework.
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.