Le framework Quarkus: comment une architecture propre y est implémentée

Bonjour, Habr!



Alors que nous continuons notre exploration des nouveaux frameworks Java et votre intérêt pour le livre Spring Boot, nous examinons le nouveau framework Quarkus pour Java. Vous pouvez en trouver une description détaillée ici , et aujourd'hui nous vous proposons de lire la traduction d'un article simple démontrant combien il est pratique d'adhérer à une architecture propre avec Quarkus .



Quarkus acquiert rapidement le statut de framework qui ne peut être évité. J'ai donc décidé de le revoir une fois de plus et de vérifier dans quelle mesure il disposait à adhérer aux principes de Pure Architecture.



Comme point de départ, j'ai pris un projet Maven simple qui comporte 5 modules standard pour créer une application CRUD REST en suivant les principes d'architecture propre:



  • domain: objets de domaine et interfaces de passerelle pour ces objets
  • app-api: interfaces applicatives correspondant à des cas pratiques
  • app-impl: mise en œuvre de ces cas au moyen du domaine. Dépend de app-apiet domain.
  • infra-persistence: Implémente des passerelles qui permettent au domaine d'interagir avec l'API de base de données. Cela dépend domain.
  • infra-web: Ouvre les cas considérés pour interagir avec le monde extérieur à l'aide de REST. Cela dépend app-api.


De plus, nous allons créer un module main-partitionqui servira d'artefact d'application déployable.



Lorsque vous prévoyez de travailler avec Quarkus, la première étape consiste à ajouter la spécification de nomenclature au fichier POM de votre projet. Cette nomenclature gérera toutes les versions des dépendances que vous utilisez. Vous devrez également configurer des plugins standard pour les projets maven dans votre outil de gestion de plugins, tels que le plugin surefire . Lorsque vous travaillez avec Quarkus, vous configurerez également ici le plugin du même nom. Enfin, vous devez ici configurer le plugin pour qu'il fonctionne avec chacun des modules (dans <build> <plugins> ... </plugins> </build>), à savoir le plugin Jandex... Puisque Quarkus utilise CDI, le plugin Jandex ajoute un fichier d'index à chaque module; le fichier contient des enregistrements de toutes les annotations utilisées dans ce module et des liens indiquant où quelle annotation est utilisée. En conséquence, CDI est beaucoup plus facile à gérer et beaucoup moins de travail à faire plus tard.



Maintenant que la structure de base est prête, vous pouvez commencer à créer une application complète. Pour ce faire, vous devez vous assurer que la partition principale crée l'application exécutable Quarkus. Ce mécanisme est illustré dans tout exemple de «démarrage rapide» fourni dans Quarkus.



Tout d'abord, nous configurons la compilation pour utiliser le plugin 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>


Ensuite, ajoutons des dépendances à chacun des modules d'application, où elles se trouveront avec les dépendances quarkus-resteasyet quarkus-jdbc-mysql. Dans la dernière dépendance, vous pouvez remplacer la base de données par celle que vous préférez (étant donné que plus tard, nous allons emprunter le chemin de développement natif et que nous ne pouvons donc pas utiliser de base de données intégrée, par exemple H2).



Vous pouvez également ajouter un profil afin de pouvoir créer l'application native ultérieurement. Pour ce faire, vous avez vraiment besoin d'un support de développement supplémentaire (GraalVM native-imageet XCode si vous utilisez OSX).



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


Maintenant, si vous exécutez mvn package quarkus:devdepuis la racine du projet, vous aurez une application Quarkus fonctionnelle! Il n'y a pas encore grand chose à voir, car nous n'avons pas encore de contrôleurs ou de contenu.



Ajout d'un contrôleur REST



Dans cet exercice, passons de la périphérie au cœur. Tout d'abord, créons un contrôleur REST qui renverra les données utilisateur (dans cet exemple, il ne s'agit que du nom).



Pour utiliser l'API JAX-RS, une dépendance doit être ajoutée au POM infra-web:



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


Le code de contrôleur le plus simple ressemble à ceci:



@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 nous exécutons l'application maintenant, nous pouvons appeler localhost : 8080 / customer et le voir Joeau format JSON.



Ajouter un cas spécifique



Ensuite, ajoutons un cas et une implémentation pour ce cas pratique. Nous allons app-apidéfinir le cas suivant:



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

    class Response {
        private String name;

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

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


Le app-implcréer une implémentation simple de cette interface.



@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"));
    }
}


Pour que CDI puisse voir le composant GetCustomersImpl, vous avez besoin d'une annotation personnalisée UseCasetelle que définie ci-dessous. Vous pouvez également utiliser le standard ApplicationScoped et l'annotation Transactional, mais en créant votre propre annotation, vous avez la possibilité de regrouper le code de manière plus logique et de détacher votre code d'implémentation des frameworks tels que CDI.



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


Pour utiliser les annotations CDI, vous devez ajouter les dépendances suivantes au fichier POM app-implen plus des dépendances app-apiet domain.



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


Ensuite, nous devons modifier le contrôleur REST pour l'utiliser dans les cas 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 vous exécutez maintenant l'application et appelez localhost : 8080 / customer, vous le verrez Jimau format JSON.



Définition et implémentation du domaine



Ensuite, nous nous concentrerons sur le domaine. L'essence ici est domainassez simple, elle consiste en Customerune interface de passerelle à travers laquelle nous recevrons les consommateurs.



public class Customer {
	private String name;

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

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


Nous devons également fournir une implémentation de la passerelle avant de pouvoir commencer à l'utiliser. Nous fournissons une telle interface dans infra-persistence.



Pour cette implémentation, nous utiliserons le support JPA disponible dans Quarkus, ainsi que le framework Panache , ce qui nous facilitera un peu la vie. En plus du domaine, nous devrons ajouter la infra-persistencedépendance suivante à:



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


Tout d'abord, nous définissons l'entité JPA correspondant au consommateur.



@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;
	}
}


Lorsque vous travaillez avec Panache, vous pouvez choisir l'une des deux options suivantes: soit vos entités hériteront de PanacheEntity, soit vous utiliserez le modèle référentiel / DAO. Je ne suis pas fan du modèle ActiveRecord, donc je vais m'arrêter moi-même au référentiel, mais ce avec quoi vous allez travailler dépend de vous.



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


Maintenant que nous avons notre entité JPA et notre référentiel, nous pouvons implémenter la passerelle 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());
	}
}


Vous pouvez maintenant modifier le code dans l'implémentation de notre cas, afin qu'il utilise la passerelle.



...
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());
}
...


Nous ne pouvons pas encore démarrer notre application, car l'application Quarkus doit encore être configurée avec les paramètres de persistance requis. Dans src/main/resources/application.propertiesle module, main-partitionajoutez les paramètres suivants.



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


Pour afficher les données d'origine, nous ajouterons également le fichier import.sqldans le même répertoire à partir duquel les données sont ajoutées.



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


Si vous exécutez maintenant l'application et appelez localhost : 8080 / customer, vous le verrez Joeégalement Jimau format JSON. Nous avons donc une application complète, du REST à la base de données.



Option native



Si vous souhaitez créer une application native, vous devez le faire à l'aide de la commande mvn package -Pnative. Cela peut prendre environ quelques minutes, en fonction de votre position de développement. Quarkus est assez rapide au démarrage et sans support natif, démarre en 2-3 secondes, mais lorsqu'il est compilé dans un exécutable natif à l'aide de GraalVM, le temps correspondant est réduit à moins de 100 millisecondes. Pour une application Java, c'est extrêmement rapide.



Essai



Vous pouvez tester l'application Quarkus à l'aide du framework de test Quarkus correspondant. Si vous annotez le test @QuarkusTest, JUnit lancera d'abord le contexte Quarkus, puis exécutera le test. Un test de l'ensemble de l'application main-partitionressemblera à ceci:



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


Conclusion



À bien des égards, Quarkus est un concurrent acharné de Spring Boot. À mon avis, certaines choses dans Quarkus sont encore mieux résolues. Même si app-impl a une dépendance de framework, ce n'est qu'une dépendance pour les annotations (dans le cas de Spring, lorsque nous ajoutons spring-contextpour obtenir @Component, nous ajoutons de nombreuses dépendances de base Spring). Si vous n'aimez pas cela, vous pouvez également ajouter un fichier Java à la section principale, en utilisant l'annotation @Producesde CDI et en y créant le composant; dans ce cas, vous n'avez pas besoin de dépendances supplémentaires dans app-impl. Mais pour une raison quelconque, jakarta.enterprise.cdi-apije veux y voir moins la dépendance que la dépendance spring-context.



Quarkus est rapide, vraiment rapide. C'est plus rapide que Spring Boot avec ce type d'application. Étant donné que, selon l'architecture propre, la plupart (sinon la totalité) des dépendances du framework doivent résider à l'extérieur de l'application, le choix entre Quarkus et Spring Boot devient évident. À cet égard, l'avantage de Quarkus réside dans le fait qu'il a été immédiatement créé avec le support GraalVM à l'esprit, et donc, au prix d'un effort minimal, il vous permet de transformer l'application en une application native. Spring Boot est toujours à la traîne par rapport à Quarkus à cet égard, mais je ne doute pas qu'il rattrapera bientôt son retard.



Certes, expérimenter avec Quarkus m'a également aidé à réaliser les nombreux malheurs qui attendent ceux qui essaient d'utiliser Quarkus avec les serveurs d'applications classiques Jakarta EE. Bien qu'il n'y ait pas encore grand-chose à faire avec Quarkus, son générateur de code prend en charge une variété de technologies qui ne sont pas encore faciles à utiliser dans le contexte de Jakarta EE avec un serveur d'applications traditionnel. Quarkus couvre toutes les bases dont les personnes familiarisées avec Jakarta EE auront besoin, et son développement est beaucoup plus fluide. Il sera intéressant de voir comment l'écosystème Java peut gérer ce genre de concurrence.



Tout le code de ce projet est publié sur Github .



All Articles