El marco de Quarkus: c贸mo se implementa la arquitectura limpia en 茅l

隆Hola, Habr!



A medida que continuamos con nuestra exploraci贸n de nuevos marcos de Java y su inter茅s en el libro Spring Boot, estamos analizando el nuevo marco de Quarkus para Java. Puede encontrar una descripci贸n detallada del mismo aqu铆 , y hoy le proponemos leer la traducci贸n de un art铆culo sencillo que demuestra lo conveniente que es adherirse a una arquitectura limpia usando Quarkus .



Quarkus est谩 ganando r谩pidamente el estatus de un marco que no se puede evitar. Por eso, decid铆 repasarlo una vez m谩s y comprobar en qu茅 medida est谩 dispuesto a adherirse a los principios de Arquitectura Pura.



Como punto de partida, tom茅 un proyecto simple de Maven que tiene 5 m贸dulos est谩ndar para crear una aplicaci贸n CRUD REST siguiendo principios de arquitectura limpia:



  • domain: objetos de dominio e interfaces de puerta de enlace para estos objetos
  • app-api: interfaces de aplicaci贸n correspondientes a casos pr谩cticos
  • app-impl: implementaci贸n de estos casos a trav茅s del 谩rea tem谩tica. Depende de app-apiy domain.
  • infra-persistence: Implementa puertas de enlace que permiten que el dominio interact煤e con la API de la base de datos. Depende de domain.
  • infra-web: Abre los casos considerados para interactuar con el mundo exterior mediante REST. Depende de app-api.


Adem谩s, crearemos un m贸dulo main-partitionque servir谩 como un artefacto de aplicaci贸n desplegable.



Cuando planee trabajar con Quarkus, el primer paso es agregar la especificaci贸n BOM al archivo POM de su proyecto. Esta lista de materiales administrar谩 todas las versiones de las dependencias que utilice. Tambi茅n deber谩 configurar complementos est谩ndar para proyectos maven en su herramienta de administraci贸n de complementos, como el complemento seguro . Mientras trabaja con Quarkus, tambi茅n configurar谩 aqu铆 el complemento del mismo nombre. Por 煤ltimo, pero no menos importante, aqu铆 debe configurar un complemento para que funcione con cada uno de los m贸dulos (en <build> <plugins> ... </plugins> </build>), es decir, el complemento Jandex... Dado que Quarkus usa CDI, el complemento Jandex agrega un archivo de 铆ndice a cada m贸dulo; el archivo contiene registros de todas las anotaciones utilizadas en este m贸dulo y enlaces que indican d贸nde se utiliza qu茅 anotaci贸n. Como resultado, el CDI es mucho m谩s f谩cil de manejar, con mucho menos trabajo que hacer despu茅s.



Ahora que la estructura b谩sica est谩 lista, puede comenzar a crear una aplicaci贸n completa. Para hacer esto, debe asegurarse de que la partici贸n principal cree la aplicaci贸n ejecutable de Quarkus. Este mecanismo se ilustra en cualquier ejemplo de "inicio r谩pido" proporcionado en Quarkus.



Primero, configuramos la compilaci贸n para usar el complemento de Quarkus:



<build>
  <plugins>
    <plugin>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-maven-plugin</artifactId>
      <executions>
        <execution>
          <goals>
            <goal>build</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>


A continuaci贸n, agreguemos dependencias a cada uno de los m贸dulos de la aplicaci贸n, donde estar谩n junto con las dependencias quarkus-resteasyy quarkus-jdbc-mysql. En la 煤ltima dependencia, puedes reemplazar la base de datos por la que m谩s te guste (considerando que luego vamos a ir por la ruta de desarrollo nativo, y por lo tanto no podemos usar una base de datos embebida, por ejemplo H2).



Alternativamente, puede agregar un perfil para poder construir la aplicaci贸n nativa m谩s adelante. Para hacer esto, realmente necesita un soporte de desarrollo adicional (GraalVM native-imagey XCode si est谩 usando OSX).



<profiles>
  <profile>
    <id>native</id>
    <activation>
      <property>
        <name>native</name>
      </property>
    </activation>
    <properties>
      <quarkus.package.type>native</quarkus.package.type>
    </properties>
  </profile>
</profiles>


隆Ahora, si ejecuta mvn package quarkus:devdesde la ra铆z del proyecto, tendr谩 una aplicaci贸n Quarkus en funcionamiento! Todav铆a no hay mucho que ver, ya que a煤n no tenemos controladores ni contenido.



Agregar un controlador REST



En este ejercicio, vayamos de la periferia al n煤cleo. Primero, creemos un controlador REST que devolver谩 los datos del usuario (en este ejemplo, este es solo el nombre).



Para usar la API JAX-RS, se debe agregar una dependencia al POM de infra-web:



<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>


El c贸digo de controlador m谩s simple se ve as铆:



@Path("/customer")
@Produces(MediaType.APPLICATION_JSON)
public class CustomerResource {
    @GET
    public List<JsonCustomer> list() {
        return getCustomers.getCustomer().stream()
                .map(response -> new JsonCustomer(response.getName()))
                .collect(Collectors.toList());
    }

    public static class JsonCustomer {
        private String name;

        public JsonCustomer(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }


Si ejecutamos la aplicaci贸n ahora, podemos llamar a localhost : 8080 / customer y verla Joeen formato JSON.



Agregar un caso espec铆fico



A continuaci贸n, agreguemos un caso y una implementaci贸n para este caso pr谩ctico. Vamos a app-apidefinir el siguiente caso:



public interface GetCustomers {
    List<Response> getCustomers();

    class Response {
        private String name;

        public Response(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }
}


El app-implcrear una implementaci贸n sencilla de esta interfaz.



@UseCase
public class GetCustomersImpl implements GetCustomers {
    private CustomerGateway customerGateway;

    public GetCustomersImpl(CustomerGateway customerGateway) {
        this.customerGateway = customerGateway;
    }

    @Override
    public List<Response> getCustomers() {
        return Arrays.asList(new Response("Jim"));
    }
}


Para que CDI vea el componente GetCustomersImpl, necesita una anotaci贸n personalizada UseCasecomo se define a continuaci贸n. Tambi茅n puede usar el ApplicationScoped est谩ndar y la anotaci贸n Transactional, pero al crear su propia anotaci贸n, obtiene la capacidad de agrupar el c贸digo de manera m谩s l贸gica y separar su c贸digo de implementaci贸n de marcos como CDI.



@ApplicationScoped
@Transactional
@Stereotype
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
}


Para usar anotaciones CDI, debe agregar las siguientes dependencias al archivo POM app-impladem谩s de las dependencias app-apiy domain.



<dependency>
  <groupId>jakarta.enterprise</groupId>
  <artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>
<dependency>
  <groupId>jakarta.transaction</groupId>
  <artifactId>jakarta.transaction-api</artifactId>
</dependency>


A continuaci贸n, necesitamos modificar el controlador REST para usarlo en los casos app-api.



...
private GetCustomers getCustomers;

public CustomerResource(GetCustomers getCustomers) {
    this.getCustomers = getCustomers;
}

@GET
public List<JsonCustomer> list() {
    return getCustomers.getCustomer().stream()
            .map(response -> new JsonCustomer(response.getName()))
            .collect(Collectors.toList());
}
...


Si ahora ejecuta la aplicaci贸n y llama a localhost : 8080 / customer, lo ver谩 Jimen formato JSON.



Definici贸n e implementaci贸n del dominio



A continuaci贸n, nos centraremos en el dominio. La esencia aqu铆 es domainbastante simple, consiste en Customeruna interfaz de puerta de enlace a trav茅s de la cual recibiremos a los consumidores.



public class Customer {
	private String name;

	public Customer(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}
}
public interface CustomerGateway {
	List<Customer> getAllCustomers();
}


Tambi茅n necesitamos proporcionar una implementaci贸n para la puerta de enlace antes de que podamos comenzar a usarla. Proporcionamos dicha interfaz en formato infra-persistence.



Para esta implementaci贸n, usaremos el soporte JPA disponible en Quarkus, y tambi茅n usaremos el framework Panache , que nos har谩 la vida un poco m谩s f谩cil. Adem谩s del dominio, tendremos que agregar la infra-persistencesiguiente dependencia:



<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>


Primero, definimos la entidad JPA correspondiente al consumidor.



@Entity
public class CustomerJpa {
	@Id
	@GeneratedValue
	private Long id;
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}


Cuando trabaje con Panache, puede elegir una de dos opciones: sus entidades heredar谩n PanacheEntity o usar谩 el patr贸n de repositorio / DAO. No soy un fan谩tico del patr贸n ActiveRecord, as铆 que me detendr茅 en el repositorio yo mismo, pero lo que trabajar谩 depende de usted.



@ApplicationScoped
public class CustomerRepository implements PanacheRepository<CustomerJpa> {
}


Ahora que tenemos nuestra entidad y repositorio JPA, podemos implementar la puerta de enlace Customer.



@ApplicationScoped
public class CustomerGatewayImpl implements CustomerGateway {
	private CustomerRepository customerRepository;

	@Inject
	public CustomerGatewayImpl(CustomerRepository customerRepository) {
		this.customerRepository = customerRepository;
	}

	@Override
	public List<Customer> getAllCustomers() {
		return customerRepository.findAll().stream()
				.map(c -> new Customer(c.getName()))
				.collect(Collectors.toList());
	}
}


Ahora puedes cambiar el c贸digo en la implementaci贸n de nuestro caso, para que use la puerta de enlace.



...
private CustomerGateway customerGateway;

@Inject
public GetCustomersImpl(CustomerGateway customerGateway) {
    this.customerGateway = customerGateway;
}

@Override
public List<Response> getCustomer() {
    return customerGateway.getAllCustomers().stream()
            .map(customer -> new GetCustomers.Response(customer.getName()))
            .collect(Collectors.toList());
}
...


No podemos iniciar nuestra aplicaci贸n todav铆a, porque la aplicaci贸n de Quarkus a煤n debe configurarse con los par谩metros de persistencia requeridos. En src/main/resources/application.propertiesel m贸dulo, main-partitionagregue los siguientes par谩metros.



quarkus.datasource.url=jdbc:mysql://localhost/test
quarkus.datasource.driver=com.mysql.cj.jdbc.Driver
quarkus.hibernate-orm.dialect=org.hibernate.dialect.MySQL8Dialect
quarkus.datasource.username=root
quarkus.datasource.password=root
quarkus.datasource.max-size=8
quarkus.datasource.min-size=2
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.sql-load-script=import.sql


Para ver los datos originales, tambi茅n agregaremos el archivo import.sqlal mismo directorio desde el cual se agregan los datos.



insert into CustomerJpa(id, name) values(1, 'Joe');
insert into CustomerJpa(id, name) values(2, 'Jim');


Si ahora ejecuta la aplicaci贸n y la llamada localhost : 8080 / cliente, ver谩 Joeque Jimen el formato JSON tambi茅n. Entonces, tenemos una aplicaci贸n completa, desde REST hasta la base de datos.



Opci贸n nativa



Si desea crear una aplicaci贸n nativa, debe hacerlo con el comando mvn package -Pnative. Esto puede tardar unos minutos, dependiendo de cu谩l sea su soporte de desarrollo. Quarkus es bastante r谩pido al inicio y sin soporte nativo, comienza en 2-3 segundos, pero cuando se compila en un ejecutable nativo usando GraalVM, el tiempo correspondiente se reduce a menos de 100 milisegundos. Para una aplicaci贸n Java, eso es incre铆blemente r谩pido.



Pruebas



Puede probar la aplicaci贸n Quarkus utilizando el marco de prueba de Quarkus correspondiente. Si anota la prueba @QuarkusTest, JUnit primero iniciar谩 el contexto de Quarkus y luego ejecutar谩 la prueba. Una prueba de toda la aplicaci贸n main-partitionse ver谩 as铆:



@QuarkusTest
public class CustomerResourceTest {
	@Test
	public void testList() {
		given()
				.when().get("/customer")
				.then()
				.statusCode(200)
				.body("$.size()", is(2),
						"name", containsInAnyOrder("Joe", "Jim"));
	}
}


Conclusi贸n



En muchos sentidos, Quarkus es un feroz competidor de Spring Boot. En mi opini贸n, algunas cosas en Quarkus est谩n a煤n mejor resueltas. Aunque app-impl tiene una dependencia del framework, es solo una dependencia para las anotaciones (en el caso de Spring, cuando agregamos spring-contextpara obtener @Component, agregamos muchas dependencias centrales de Spring). Si no le gusta esto, tambi茅n puede agregar un archivo Java a la secci贸n principal, usando la anotaci贸n @Producesde CDI y creando el componente all铆; en este caso, no necesita ninguna dependencia adicional en app-impl. Pero por alguna raz贸n, jakarta.enterprise.cdi-apiquiero ver la adicci贸n menos que la adicci贸n spring-context.



Quarkus es r谩pido, muy r谩pido. Es m谩s r谩pido que Spring Boot con este tipo de aplicaci贸n. Dado que, seg煤n la Arquitectura limpia, la mayor铆a (si no todas) de las dependencias del marco deben residir en el exterior de la aplicaci贸n, la elecci贸n entre Quarkus y Spring Boot se vuelve obvia. En este sentido, la ventaja de Quarkus es que se cre贸 de inmediato teniendo en cuenta el soporte de GraalVM y, por tanto, a costa de un m铆nimo esfuerzo, permite convertir la aplicaci贸n en nativa. Spring Boot todav铆a est谩 por detr谩s de Quarkus en este sentido, pero no tengo ninguna duda de que se pondr谩 al d铆a pronto.



Es cierto que experimentar con Quarkus tambi茅n me ayud贸 a darme cuenta de las muchas desgracias que aguardan a quienes intenten utilizar Quarkus con los servidores de aplicaciones cl谩sicos de Jakarta EE. Si bien todav铆a no se puede hacer mucho con Quarkus, su generador de c贸digo admite una variedad de tecnolog铆as que a煤n no son f谩ciles de usar en el contexto de Jakarta EE con un servidor de aplicaciones tradicional. Quarkus cubre todos los aspectos b谩sicos que necesitar谩n las personas familiarizadas con Jakarta EE, y el desarrollo es mucho m谩s sencillo. Ser谩 interesante ver c贸mo el ecosistema Java puede manejar este tipo de competencia.



Todo el c贸digo de este proyecto est谩 publicado en Github .



All Articles