Architecture

Prévalence d’objets

Publié le : Auteur: Anteo Consulting 2 commentaires
architecture

Jeudi 14 et vendredi 15 juin se déroulait à Rennes la seconde édition du BreizhCamp, deux journées de conférences de « mix technologic » organisées par le BreizhJug sur les plateformes, les langages, les frameworks, les outils, .. bref l’univers du développeur en général et en particulier.

L’occasion de s’enrichir, de s’ouvrir à de nouvelles techniques et technologies, et d’échanger avec des gens pointus entre les sessions dans une ambiance conviviale.

Et l’occasion, au détour d’une présentation, de redécouvrir un sujet lointainement familier !

Qu’est ce que la prévalence d’objet ?

L’objectif de cette technique est de se passer de base de données (et d’ORM) au profit d’une gestion en mémoire de l’ensemble des données, couplée à des méchanismes de prise d’instantanés et de journalisation assurant leur persistance.
Cette idée a été formulée dès 1987 par Andrew Birrell, Michael Jones et Ted Wobber dans « A Simple and Efficient Implementation for Small Databases » puis poussée, depuis le début des années 2000, par Klaus Wuestefeld avec une implémentation de référence pour Java : Prevayler.
Il en existe aujourd’hui plusieurs autres pour .Net, Ruby, python, Smalltalk, Objective-C, ..

Le modèle

Un système prévalent repose sur deux points clefs :

  • un graphe d’objets métier sérialisables, en mémoire, avec un point d’entrée unique.

Nous gérerons dans cet exemple des comptes banquaires, avec un point d’entrée qui n’a rien de métier, et que j’ai nommé DataStore :

  • et l’utilisation d’objets de commandes (également sérialisables), réifications des modifications à apporter aux données, pour l’ensemble des mises à jour.

Voici par exemple la création de comptes dans notre système :

Petite parenthèse, rien à voir avec la prévalence : Les annotations @Data, @NonNull, @RequiredArgsConstructor sont des facilitateurs du très intéressant projet Lombok qui permettent de générer accesseurs et constructeurs à la compilation pour des sources plus lisibles.

Le fonctionnement

Les objets de commandes, qui portent le seul code habilité à modifier le graphe d’objets métier, sont soumis à Prevayler qui va les exécuter séquentiellement.

Chaque commande est journalisée avant d’être appliquée aux données. A intervalle régulier, un nouvel instantané du graphe de données métier est pris et sauvegardé sur disque.

La journalisation et la prise d’instantanés utilisent les méchanismes de sérialisation standard de Java par défaut, avec toutefois la possibilité d’utiliser alternativement du JSON ou du XML via Skaringa ou XStream.

A tout moment, l’application peut ainsi s’arrêter. Au redémarrage de celle-ci, le graphe d’objets métier en mémoire est reconstruit en rechargeant le dernier instantané depuis le disque et en rejouant les commandes journalisées depuis la prise de cet instantané.

Voici un exemple de manipulation de notre graphe en mémoire.

A l’inverse des modifications, les accès en lecture ne sont pas nécessairement encapsulés dans des objets de commandes.

ACIDité des commandes

La commande constitue un substitut aux transactions offertes par les méthodes traditionnelles d’accès aux données. Elle présente toutefois des caractéristiques bien spécifiques

  • L’application d’une commande est Durable. Les modifications apportées par une commande exécutée par Prevayler ne peuvent être perdues.
  • L’application d’une commande n’est pas nécessairement Atomique. C’est au code de la commande de s’assurer qu’aucune modification n’est apportée dans le cas où toutes ne peuvent être réalisées. La technique préconisée par les concepteurs de Prevayler est d’utiliser un système de préconditons.

Voici par exemple ce que pourrait donner le virement de compte à compte en utilisant les préconditons Guava

  • De la même manière, il n’existe pas de garantie de Cohérence, c’est à dire que Prevayler n’offre pas de système automatique de vérification d’invariants (unicité, champs non null, cardinalité des relations, ..).
  • Enfin l’isolation est partielle :

Les commandes, étant sérialisées, sont parfaitement isolées les unes des autres.
Si les accès en lecture sont également encapsulés dans des commandes, le système bénéficie du plus haut niveau d’isolation des bases de données (« Serializable »).
A l’inverse si elles ne le sont pas, elles se déroulent totalement hors « transaction », avec l’équivalent du « Read uncommitted ». Un peu comme si vous réalisiez vos lectures en ouvrant les fichiers d’une base de données tandis que les écritures passent par son moteur transactionnel.

Avantages

  • Très rapide ! Pas d’accès disque, pas de communication inter processus, pas d’instrumentation des objets métier, pas de gestion de caches, .. uniquement des parcours de graphes d’objets en mémoire.
  • Le modèle mémoire se trouve totalement libéré des contraintes apportées traditionnellement par la persistance. Plus de contraintes de granularité, d’héritage, ..
  • Facile à coder, pas de problèmes insolubles, pas de mauvaises surprises de tenue de charge. Le modèle est simple, vous gardez la totale maîtrise de la complexité des accès aux données, en modification comme en lecture.
  • S’intègre très bien dans une approche DDD et embrasse le pattern CQS.

Inconvénients

  • Limitation à quelques Go de mémoire, donc quelques millions ou dizaines de millions d’objets. A noter que rien n’empêche de garder les contenus lourds (images, audio, vidéo) dans une réelle base no-sql à part pour repousser cette limite.
  • Pas de système de requêtage par défaut, mais des parcours ad hoc ou l’utilisation de bibiothèques telles que JXPath.
  • Pas d’indexation : La recherche de tous les comptes dont l’id commence par ‘c’ nécessite une structuration spécifique pour être efficace sur de gros volumes (mais n’est-il pas plus rapide de parcourir l’ensemble des comptes que d’exécuter cette même requête sur une base de données externe ?)
  • Pas d’intégrité déclarative comme cela a déjà été évoqué.
  • L’évolution du schéma est un exercice délicat, pour lequel toutefois plusieurs techniques sont décrites.
  • L’accès est limité à un langage (si sérialisation standard), à un unique runtime (quoiqu’une solution de réplicat en lecture seule soit en développement dans Prevayler).
  • L’absence d’outils permettant d’explorer les instantanés comme on peut le faire sur une base de données. Corriger une anomalie métier dans les données nécessite un développement spécifique, remonter ces données dans un BI également, etc.
  • Il ne peut pas s’inscrire dans un système de transactions distribuées.

Et aujourd’hui ?

La communauté autour de ces solutions de prévalence est bien vivante.

Et c’est là que l’on en revient au BreizhCamp et plus précisement à Jean-Philippe Gouigoux et Damien Gaillard (Société MGDIS), qui présentaient leur travaux autour de Bamboo, LA solution de prévalence .Net.
Leur session intitulée « Prévalence objet : comment migrer » adressait une problématique conséquence directe de l’absence des possibilités de requêtage standards : comment migrer une application bâtie sur un SGBD vers Bamboo ?
Pour y répondre, ils ont opté pour une approche élégante en développant en interne un connecteur ADO.NET pour Bamboo permettant à quelques ajustements près d’utiliser les DAO existants pour interroger le graphe d’objets métier en mémoire.

On ne peut que souhaiter que ce connecteur puisse être prochainement contribué sous une licence ouverte. Les auteurs de Prevayler (licence BSD) ou de Bamboo (licence MIT) l’ont bien compris : au delà de l’effort initial, l’ouverture du code à la communauté est la seule approche viable pour fiabiliser, étendre et pérenniser ces briques purement techniques. Et il serait dommage que ce connecteur reste dans un placard !

Et demain ?

A quand un driver JDBC pour Prevayler 🙂 ?
A quand un visualiseur/requêteur universel pour Prevayler, scriptable en groovy, permettant optionnellement de charger les classes du modèle depuis un jar pour une navigation facilitée dans le graphe ?  Et pour Bamboo, un plugin linqpad ?

  • arnaud

    Merci pour l’article! 🙂
    J’espère que ça va rendre un peu plus populaire ce genre de solution qui le mérite.
    (Mais attention aux idées reçues sur les base de données, surtout dans certains milieux.)

    J’aurais tendance à dire que masquer les POJOs du modèle derrière une API générique type JDBC fait perdre la moitié de l’intérêt d’une persistance à la Prevayler. C’est quand même plus sympa de faire ça avec des API de collections genre Guava FluentIterable que de devoir utiliser un language/script de requête, et c’est mieux « typé ».

    « L’évolution du schéma est un exercice délicat »
    Mmm, je dirais que justement c’est beaucoup plus simple et robuste que pour des bases SQL. Pas besoin de migrer les schémas « en masse » en risquant de perdre des données et/ou en perdant de l’historique, par exemple il suffit de garder l’ancienne TransactionDeCompte qui connait l’ancien format de sérialisation (mais est capable de se jouer sur le model courant), et de faire une TransactionDeCompteV2 qui utilise un nouveau format.

    « Limitation à quelques Go de mémoire » oui mais 2 points à noter
    – c’est possible d’optimiser pas mal l’utilisation mémoire avec certaines techniques genre Guava Interners (ou String.intern), en évitant trop de petites HashMaps, etc
    – avec une JVM 64-bit (avec option « compressed-pointers ») et les algos de GC récents, le nombre d’objets possibles dans une seule JVM augmente régulèrement

  • JP Gouigoux

    Un grand merci également pour cet article, très clair et pédagogique. Pour le connecteur pour Bamboo, il nous reste encore un peu de travail sur le GROUP BY, qui nécessite de créer des classes anonymes dynamiquement, mais ça va le faire pour la fin de l’été, je pense. J’espère avoir l’accord de ma société pour le fournir en Open Source d’ici la fin de l’année.

    Entièrement d’accord avec les commentaires précédents : le fait de travailler directement sur le modèle métier rend les migrations beaucoup plus simples. Représenter un modèle métier en table est déjà compliqué, mais créer le SQL pour faire évoluer des tables de manière correspondante à l’évolution du métier, c’est un nid à erreur. Pouvoir monter en version directement sur l’objet, c’est un bonheur.

    Quand à la limitation sur la RAM, avec des limites de RAM très hautes sur les gros serveurs, et une sérialisation correctement réalisée, ça ne pose pas trop de problème en pratique. De toute façon, au dessus de 100 Go, il faut plutôt raisonner en termes de Big Data que de prévalence objet.