
Aujourd'hui, l'équipe Micronaut d' Object Computing Inc (OCI) a présenté Predator , un nouveau projet open source dont l'objectif est d'améliorer considérablement le temps d'exécution et les performances (depuis la mémoire) de l'accès aux données pour les microservices et les applications sans serveur, sans perte de productivité par rapport à des outils comme GORM et Spring Data.
Historique des outils d'accès aux données
Nous pouvons suivre l'historique du modèle de référentiel de données depuis 2004, lorsque Ruby on Rails est sorti avec le sous-système ActiveRecord, une API qui a révolutionné notre compréhension de l'accès aux données en termes de productivité des développeurs.
En 2007, l'équipe Grails a introduit pour la première fois une API de type ActiveRecord pour la JVM - GORM (qui fait partie de Grails). GORM s'est appuyé sur la nature dynamique de Groovy pour implémenter des méthodes de recherche par-dessus Hibernate et a fourni les mêmes avantages de productivité aux utilisateurs de la JVM.
Étant donné que GORM dépend du langage Groovy, un projet Spring Data a été créé en 2011 qui a permis aux développeurs Java de définir des méthodes de recherche, telles que findByTitle
, dans l'interface et d'implémenter automatiquement la logique de requête au moment de l'exécution.
Fonctionnement des outils d'accès aux données
Toutes les implémentations mentionnées utilisent le même modèle, qui consiste à créer un métamodèle d'entités de projet au moment de l'exécution qui modélise les relations entre vos classes d'entités. Dans Spring Data, il s'agit d'un MappingContext et dans GORM, il est également appelé MappingContext. Ils sont construits en scannant les classes en utilisant la réflexion. (La similitude de dénomination n'est pas accidentelle ici. En 2010, j'ai travaillé avec l'équipe de Spring Data pour essayer de recréer GORM pour Java, sur un projet qui s'est finalement transformé en ce qu'on appelle aujourd'hui Spring Data)
Ce métamodèle est ensuite utilisé pour transformer une expression de recherche, telle que bookRepository.findByTitle("The Stand")
, en un modèle de requête abstrait au moment de l'exécution en utilisant une combinaison d'analyse d'expression régulière et de logique de structure. Nous avons besoin d'un modèle de requête abstrait car le dialecte cible de la requête est différent pour chaque base de données (SQL, JPA-QL, Cypher, Bson, etc.)
Prise en charge du référentiel Micronaut
Depuis le lancement de Micronaut il y a un peu plus d'un an, la principale fonctionnalité manquante qui nous a été posée était "GORM for Java" ou le support Spring Data. De nombreux développeurs sont amoureux de la productivité fournie par ces outils, ainsi que de la facilité de définition des interfaces que le framework implémente. Je dirais que la plupart des succès de Grails et Spring Boot peuvent être attribués respectivement à GORM et Spring Data.
Pour les utilisateurs de Micronaut utilisant Groovy, nous avions le support GORM dès le premier jour, et les utilisateurs Java et Kotlin n'avaient rien, car ils devaient implémenter eux-mêmes des référentiels.
Il serait techniquement possible, et franchement plus facile, d'ajouter simplement un module pour Micronaut qui configurerait Spring Data. Cependant, en suivant ce chemin, nous fournirions un sous-système implémenté en utilisant toutes les méthodes que Micronaut a essayé d'éviter: utilisation généralisée des proxys, réflexion et consommation élevée de mémoire.
Présentation de Predator!
Predator, abréviation de Precomputed Data Repositories, utilise l'API Micronaut pour compiler avant l'exécution (AoT, à l'avance) pour transférer un méta-modèle d'entités et convertir les expressions de recherche (telles que findByTitle
) en SQL ou JPA-QL approprié dans votre compilateur . Par conséquent, la requête exécute une couche d'exécution de programme très fine sans réflexion, et il ne lui reste plus qu'à exécuter la requête et à renvoyer les résultats.
Le résultat est écrasant ... le démarrage à froid est considérablement réduit, nous obtenons une consommation de mémoire incroyablement faible et une nette amélioration des performances.
Aujourd'hui, nous ouvrons le code source de Predator sous la licence Apache 2, il viendra avec deux implémentations initiales (plus de fonctionnalités prévues pour l'avenir) pour JPA (basé sur Hibernate) et pour SQL avec JDBC.
L'implémentation JDBC me plaît le plus, car elle est complètement indépendante de la réflexion, n'utilise pas de proxy et de chargement de classe dynamique pour votre niveau d'accès aux données, ce qui améliore les performances. La couche d'exécution est si légère que même le code de référentiel équivalent écrit à la main ne s'exécutera pas plus rapidement.
Performance Predator
Étant donné que Predator n'a pas besoin d'exécuter de transformations de requête au moment de l'exécution, le gain de performances est important. Dans le monde de l'utilisation du cloud computing, où vous payez pour la durée d'exécution de votre application ou pour l'exécution d'une seule fonction, les développeurs perdent souvent de vue les performances de leurs mécanismes d'accès aux données.
Le tableau suivant résume les différences de performances qui peuvent être attendues pour une expression de recherche simple, telle que findByTitle
, par rapport à d'autres implémentations. Tous les tests ont été réalisés à l'aide d'un banc de test sur le Xeon iMac Pro 8 cœurs dans les mêmes conditions, les tests sont ouverts et se trouvent dans le référentiel :
Oui, vous l'avez bien lu. Avec Predator JDBC, vous pouvez vous attendre à une augmentation des performances de près de 4X par rapport à GORM et de 2,5X par rapport à Spring Data.
Et même si vous utilisez Predator JPA, vous pouvez compter sur plus de 2 fois les performances améliorées par rapport à GORM et jusqu'à 40% d'augmentation par rapport à Spring Data JPA.
Regardez la différence de taille de la pile d'exécution lors de l'utilisation de Predator par rapport aux alternatives:
Prédateur:

Predator JPA:

Données de printemps:

GORM:

Predator JDBC n'utilise que 15 images jusqu'au moment où votre demande est exécutée, tandis que Predator JPA en utilise 30 (principalement en raison de la mise en veille prolongée), par rapport à plus de 50 images de pile dans Spring Data ou GORM. Et tout cela grâce aux mécanismes AOP Micronaut qui n'utilisent pas la réflexion.
Des stackraces plus courts simplifient également le débogage des applications. L'un des avantages de la plupart du travail pendant la compilation est que les erreurs peuvent être détectées avant le lancement de l'application, ce qui améliore considérablement l'expérience du développeur. Nous obtenons des erreurs de compilation immédiatement au lieu des erreurs d'exécution pour les erreurs les plus courantes.
Compilation des contrôles de temps
La plupart des implémentations du modèle de référentiel reposent uniquement sur l'exécution de toutes les opérations au moment de l'exécution. Cela signifie que si le développeur fait une erreur dans la définition de l'interface du référentiel, les erreurs ne seront pas visibles jusqu'à ce que l'application soit réellement lancée.
Cela nous prive de certains des avantages de Java pour la vérification de type et nous avons une mauvaise expérience des données. Ce n'est pas le cas avec Predator. Prenons l'exemple suivant:
@JdbcRepository(dialect = Dialect.H2) public interface BookRepository extends CrudRepository<Book, Long> { Book findByTile(String t); }
Ici, BookRepository
nous avons déclaré une demande à un objet nommé Book
, qui a une propriété title
. Malheureusement, il y a une erreur dans cette déclaration: nous avons nommé la méthode findByTile
au lieu de findByTitle
. Au lieu d'exécuter ce code, Predator ne permettra pas à votre code de se compiler avec un message d'erreur informatif:
Error:(9, 10) java: Unable to implement Repository method: BookRepository.findByTile(String title). Cannot use [Equals] criterion on non-existent property path: tile
De nombreux aspects de Predator sont vérifiés lors de la compilation, lorsque cela est possible, pour garantir qu'une erreur d'exécution n'est pas provoquée par une déclaration de référentiel incorrecte.
Substrat JDBC et GraalVM Predator
Une autre raison pour laquelle Predator devrait être satisfait est qu'il est compatible avec les images GraalVM Substrate natives et ne nécessite pas de conversions de bytecode complexes lors de la construction, contrairement à celles d'Hibernate sur GraalVM.
En éliminant complètement la réflexion et les proxys dynamiques de la couche d'accès aux données, Predator simplifie considérablement la création d'applications qui fonctionnent avec des données s'exécutant sur GraalVM.
L'exemple d'application Predator JDBC s'exécute sur Substrate sans problème et vous permet de créer une image native beaucoup plus petite (25 Mo de moins!) Qu'Hibernate a besoin de fonctionner, grâce à une couche d'exécution beaucoup plus fine.
Nous avons vu le même résultat lorsque nous avons implémenté la compilation des règles de validation de bean pour Micronaut 1.2. La taille de l'image native a diminué de 10 Mo, dès que nous avons supprimé la dépendance à l'égard du validateur Hibernate, et la taille du fichier JAR de 2 Mo.
L'avantage ici est évident: en faisant plus de travail pendant la compilation et en créant des exécutions plus compactes, vous obtenez une image native plus petite et un fichier JAR, ce qui permet de déployer des microservices plus petits et plus faciles lors du déploiement via Docker. L'avenir des frameworks Java est des compilateurs plus puissants et des temps d'exécution plus petits et plus légers.
Predator et l'avenir
Nous commençons tout juste à travailler avec Predator et sommes extrêmement satisfaits des opportunités qu'il ouvre.
Initialement, nous commençons par la prise en charge de JPA et SQL, mais à l'avenir, vous pouvez vous attendre à une prise en charge de MongoDB, Neo4J, Reactive SQL et d'autres bases de données. Heureusement, ce travail est beaucoup plus simple car la plupart de Predator est en fait basé sur le code source GORM, et nous pouvons réutiliser la logique GORM pour Neo4J et GORM pour MongoDB pour publier ces implémentations plus rapidement que prévu.
Predator est l'aboutissement de la combinaison des différents éléments constitutifs de Micronaut Core qui ont permis de l'implémenter, des API AoT, qui sont également utilisées pour générer la documentation Swagger, au support relativement nouveau de Bean Introspection, qui vous permet d'analyser des objets à l'exécution sans réflexion.
Micronaut fournit des blocs de construction pour des choses incroyables. Predator en fait partie, et nous commençons à peine à travailler sur certaines des fonctionnalités prometteuses de Micronaut 1.0.
MISE À JOUR: Après l'annonce choquante, le tueur de Spring Data a été renommé Micronaut Data: https://micronaut-projects.imtqy.com/micronaut-data/1.0.x/guide/