Cherche feedback sur un mini-ORM SQL en Java

22 views
Skip to first unread message

Julien Kirch

unread,
Oct 4, 2016, 5:03:34 PM10/4/16
to tec...@googlegroups.com
Hello,

suite à un projet où l’équipe utilisait Hibernate et a passé quelques heures à débuguer des trucs stupides, je me suis décidé à faire en projet « week-end » un mini ORM SQL en Java pour voir si on peut faire mieux.

C’est une approche très Java : ça se fait en générant du java (et pas des classes) depuis une description du modèle de donnée.
Donc très peu de magie et beaucoup de type-safety à la compilation : par exemple si vous passez un champ de nullable à non nullable dans le modèle et que dans une requête vous avez un critère « ce champ est null » ça ne compilera plus car la constante qui vous servait à ça n’existera plus.

Si vous avez un peu de temps, je prends des feedback / questions / remarques incendiaires : https://github.com/archiloque/better-sql-orm-in-java

Julien

Francois-Xavier Bonnet

unread,
Oct 5, 2016, 1:21:49 AM10/5/16
to tec...@googlegroups.com
Hello,

J'aime beaucoup l'idée:
  • faire un maximum de choses à la compilation
  • l'utilisation de Javapoet pour générer du code élégamment
  • la séparation entre le code generator et le parsing du xml qui me permet d'autres usages si je n'ai pas envie de faire un fichier xml de mapping
Pour moi l'autre gros défaut des ORM classiques : on ne veut pas que le développeur fasse du SQL et ça c'est dommage car l'ORM n'est capable d'utiliser en général qu'une toute petite partie des possibilités offerte par SQL.
Plutôt que de dépenser de l'énergie à écrire un générateur qui gère les clés composites et génère tout seul les différents types de jointures et autres cas encore plus complexes qu'on peut rencontrer, je préférerais un outil qui me permette d'écrire moi-même mes requêtes SQL et soit capable de les comprendre et de me générer du code à la compilation pour les utiliser. J'avais fait un POC en ce sens il y a quelques temps ou je récupérais les requêtes SQL, j'en faisais un PreparedStatement et j'utilisais les métadonnées obtenues pour générer du code.

Si je trouve un peu de temps je serai ravi de t'aider sur ce projet.


Julien

--
Vous recevez ce message, car vous êtes abonné au groupe Google Groupes techos.
Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le concernant, envoyez un e-mail à l'adresse techos+unsubscribe@googlegroups.com.
Pour envoyer un message à ce groupe, adressez un e-mail à tec...@googlegroups.com.
Visitez ce groupe à l'adresse https://groups.google.com/group/techos .
Pour plus d'options, visitez le site https://groups.google.com/d/optout .

Julien Kirch

unread,
Oct 5, 2016, 2:04:11 AM10/5/16
to tec...@googlegroups.com
Tu générais quel genre de code à partir des requêtes ? 

Julien

Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le concernant, envoyez un e-mail à l'adresse techos+un...@googlegroups.com.
Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse tec...@googlegroups.com.

Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
Pour obtenir davantage d'options, consultez la page https://groups.google.com/d/optout.

Laurent Caillette

unread,
Oct 5, 2016, 2:08:00 AM10/5/16
to tec...@googlegroups.com
Salut Julien, salut François-Xavier,

Je republie un message issu d'une discussion privée avec Julien, des
fois que ça intéresse quelqu'un. La proposition qui suit diverge
radicalement de l'approche formalisée dans la maquette sur github, et
ne se limite pas au requêtage car elle aborde la modélisation des
entités en général.

Pour en rester à la maquette de Julien, j'aime beaucoup la façon de
gérer les NULL. Le langage Java ne nous aide guère. D'ailleurs ma
proposition gagnerait à se baser sur Kotlin, pour qui ``Type?``
signifie une référence nullable sur un ``Type``. C'est autrement plus
élégant que les approche du genre ``Option< Type >``.

J'aime bien aussi ce que dit François-Xavier sur la possibilité
d'écrire du SQL "à mains nues" parce que si ça doit se finir comme ça,
autant l'admettre au moment de la conception. Peut-être que la
solution c'est de valider dès que possible (en phase de
post-compilation ou de test par exemple) qu'une requête SQL est bien
compatible avec le code Java qu'on vient de générer ou compiler.

Je n'ai encore rien dit sur la mort des SGBDR. Pas tout d'un coup.


== Proposition


Le projet de Julien fait nécessairement penser à JOOQ et Querydsl.
Mais on peut aussi s'inspirer d'Immutables, moins connu mais pas moins
intéressant :
https://immutables.github.io

Il y a moyen d'aborder le projet par l'un de ces deux bouts : le SQL
ou Java. Comme il n'y a pas trente-six façons de faire la même chose
en SQL, la discussion va tourner autour de Java. Autrement dit le
projet implique la définition des idiomes Java utilisés dans la partie
applicative.

Un choix bien connu c'est celui d'Hibernate. On a des grappes d'objets
muables et on ne sait pas où elles s'arrêtent, surtout si on fait du
lazy-loading.

Celui qui tourne le dos aux objets immuables se condamne lui-même à
toutes sortes de tâches répétitives et dégradantes, notamment lors de
la mise au point. Rappelons les avantages des objets immuables :
- Pas de références cycliques.
- La sérialisation est trivale.
- L'égalité est univoque (la comparaison champ par champ est triviale aussi).
- L'accès concurrent ne pose pas de problème.
- Les tests sont plus faciles à écrire.
- Une valeur de référence est une constante.
- Et comme l'égalité est univoque le test fait de meilleures vérifications.
- Vu que `95 %` des bugs sont liés à des effets de bord, en supprimant
les effets de bord ça va forcément mieux.

Par contre il y a un sérieux inconvénient : si tu veux modifier un
champ, il faut refaire toute la grappe. Mais quand tu regardes
Immutables, tu vois qu'ils fournissent un Builder qui économise
beaucoup de sueur.

J'ai bossé sur 2 projets avec des entités immuables partout et c'est
magique. Ça donne des trucs très homogènes et très robustes, une fois
que tu as isolé les bonnes pratiques.

J'ai commencé par parler des objets immuables parce qu'un tel choix va
énormément influencer la suite. Tu parles de description de format en
XML, mais quand tu regardes Immutables, tu vois qu'ils extraient les
métadonnées du code Java. De plus leur génération de code ne se fait
pas niquer en cas de renommage automatique.

Maintenant qu'on a quelques idées claires sur le Java, les ennuis
peuvent commencer. Le SQL permet de requêter de façon à ce que le
résultat destructure complètement les entités contenues dans les
tables. Par exemple tu peux requêter la table ``Person`` en faisant
une jointure sur la table des véhicules possédés, mais juste garder
l'identifiant de la personne et son nom. Ça peut se régler comme suit
:
- On fait une classe Java ``OwnedVehicle`` sans la couleur des yeux du mec.
- On réutilise la clases ``Person`` et on la bricole pour qu'un appel
à la méthode ``eyesColor()`` pète une exception. Ça c'est Hibernate.

Le second choix trahit l'intention de vérifier quoique ce soit lors de
la compilation. Le premier choix par contre nécessite de pisser un peu
plus de code parfaitement trivial, mais qui a le mérite de la clarté.

Je te parlais d'entités ``Person`` et ``Vehicle``. On va dire qu'on a
deux tables avec une relation 1-n (chaque ``Vehicle`` connaît
l'identifiant de son propriétaire). Je pense que le requêtage doit
supporter tous les cas suivants :
- Un objet ``Person`` qui référence plusieurs ``Vehicle`` via un
``ImmutableSet`` (j'aime Guava).
- Un objet ``OwnedVehicle`` qui expose certains attributs de ``Person``.
- Un objet ``Person`` qui te fournit une ``ImmutableList`` contenant
des ``Vehicle.Key``.

À propos des clés, définir une sous-classe ``Key`` pour représenter
une clé spécialisée pour chaque objet tu peux me croire, c'est
**bien**. (Après on raffine avec une interface ``Keyable< KEY >``,
ainsi que des classes de base pour les clés comme la ``LongKey``, et
des clés qui implémentent ``Comparable``.) Le fait de définir ta
propre clé permet des clés composées, qui ont leur intérêt dans le
monde Java (dans le monde SQL c'est autre chose).

Un truc aussi qui me vient à l'esprit : tu peux associer un
identifiant de génération à chacun de tes objets issu du SGBDR. Ça
peut être chouette pour la traçabilité ou le verrouillage optimiste
(un des bons trucs d'Hibernate).

Une fois que tu as extrait les métadonnées du code Java tu peux lancer
une vérification automatique de cohérence du schéma au démarrage de
l'appli.


== Du code !

Voilà une proposition vite fait.

Les classes fournies en standard :

<<<
interface Keyable< KEY > {
KEY key()
}

class LongKey implements Comparable< LongKey > {
public final long value ;
...
}

class StringKey implements Comparable< StringKey > {
public final String value ;
public StringKey( Pattern validationPattern, String value ) { ... }
...
}

interface Select< RESULT > {
Iterable< RESULT > all() ;
...
}

>>>

Les entités qui servent également de résultats de requêtes :

<<<
interface NamedPerson implements Keyable< Key > {
String firstName() ;
String lastName() ;
class Key extends LongKey { }
}

interface Person extends NamedPerson {
Color eyesColor() ;
}

interface VehicleOwner extends Person {
ImmutableSet< Vehicle > ownedVehicles() ;
}

interface Vehicle implements Keyable< LicensePlate > {
Color color() ;
class LicensePlate extends StringKey {
public LicensePlate( final String value ) {
super( REGEX, value ) ;
}
}
}

interface OwnedVehicle extends Vehicle {
NamedPerson owner() ; // Ça évite de traîner ``eyesColor``.
}

>>>

Les requêteurs (sans les opérateurs logiques, pour la concision) :

<<<
class SelectOwnedVehicle extends Select< OwnedVehicle > {
SelectOwnedVehicle licensePlate( Vehicle.LicensePlate licensePlate ) ;
SelectOwnedVehicle color( Color color ) ;
...
}

class SelectVehicleOwner extends Select< VehicleOwner > {
SelectVehicleOwner lastName( String lastName ) ;
...
}
>>>

L'idée c'est de réutiliser les entités si on veut ou si on peut, mais
il n'y a pas d'obligation. Si on veut limiter à mort le nombre de
champs, on se retrouve avec un paquet d'entités pour chaque cas
d'utilisation. Au moins c'est super-clair.

Il faut encore trouver un moyen de définir la requête, et comment
associer les colonnes de SQL aux méthodes. On pense forcément aux
annotations, mais ça débouche vite sur des trucs assez pourris. La
dernière fois que j'ai été confronté au problème, j'ai utilisé un
DynamicProxy pour récupérer l'appel de méthode, dans un contexte où on
l'utilisait comme un symbole. On peut voir un exemple ici :
https://github.com/caillette/Wrench/blob/master/src/test/java/io/github/caillette/wrench/showcase/Inspection.java#L28
(C'est le ``using.number()`` où ``number`` est une propriété à
laquelle on associe des comportements comme ``defaultValue( 1 )``.)

Dans le même genre ce truc peut donner des idées (pas que pour le requêtage):
http://benjiweber.co.uk/blog/2015/08/21/html-in-java

Avec une telle approche, on peut commencer par coder le SQL en dur
dans les classes ``Select`` sans valider grand-chose, mais ça permet
déjà d'introduire des objets immuables dans le code applicatif (ne pas
négliger l'effort d'évangélisation). Pour cette première phase, JOOQ
peut faciliter le travail. Et puis après on réécrit ce qu'il faut et
on vire JOOQ en toute discrétion.

Francois-Xavier Bonnet

unread,
Oct 5, 2016, 2:33:46 AM10/5/16
to tec...@googlegroups.com
Pour répondre à la question de Julien, mon objectif était assez simple : générer une classe qui représente la requête avec des setters pour les différents paramètres (j'utilisais une syntaxe particulière dans la requête pour nommer chacun des paramètres plutôt que d'utiliser des "?"). Ensuite générer des objets représentant les lignes de résultat avec des getters pour chaque colonne du resultset (tout à fait d'accord sur l'idée de renvoyer des objets immuables).
De cette manière tout est vérifié à la compilation : la validité de la requête sql, les paramètres, leur type et en sortie les colonnes du résultat et leur type.

Tout ça c'est pour les select car c'est la qu'on fait des requêtes compliquées en général et donc que je trouve plus pratique d'écrire du SQL.
Pour tout ce qui est create, delete, update je trouve l'approche de tout générer directement à partir de la structure de la base très pratique.


>> Pour envoyer un message à ce groupe, adressez un e-mail à
>> tec...@googlegroups.com.
>> Visitez ce groupe à l'adresse https://groups.google.com/group/techos .
>> Pour plus d'options, visitez le site https://groups.google.com/d/optout .
>
>
>
> --
> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
> "techos".
> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
> concernant, envoyez un e-mail à l'adresse

> Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
> tec...@googlegroups.com.
> Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
> Pour obtenir davantage d'options, consultez la page
> https://groups.google.com/d/optout.
>
>
> --
> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
> "techos".
> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
> concernant, envoyez un e-mail à l'adresse

> Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
> tec...@googlegroups.com.
> Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
> Pour obtenir davantage d'options, consultez la page
> https://groups.google.com/d/optout.

--
Vous recevez ce message, car vous êtes abonné au groupe Google Groupes techos.
Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le concernant, envoyez un e-mail à l'adresse techos+unsubscribe@googlegroups.com.

Philippe Kernévez

unread,
Oct 5, 2016, 8:02:52 AM10/5/16
to techos
Ce que je trouve très intéressant dans Hibernate (car évite beaucoup de bugs) c'est le cache de niveau 1 (qui porte mal son nom, ce n'est pas un cache). Il évite de représenter 2 fois en mémoire la même donnée.
Comment gérez-vous ça dans vos approches ?

a+
Philippe Kernévez



Directeur technique (Suisse),
pker...@octo.com
+41 79 888 33 32

Retrouvez OCTO sur OCTO Talk : http://blog.octo.com
OCTO Technology http://www.octo.com

Julien Kirch

unread,
Oct 5, 2016, 8:30:22 AM10/5/16
to tec...@googlegroups.com
Je n’ai pas traité le point (c’est un POC) mais ça serai facile à faire dans mon code, ça demanderait une gestion de session.

Après ça permet d’autre trucs, comme d’avoir des warning quand tu modifies des objets sans les persister ensuite.

Julien

Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le concernant, envoyez un e-mail à l'adresse techos+un...@googlegroups.com.

Laurent Caillette

unread,
Oct 5, 2016, 8:52:19 AM10/5/16
to tec...@googlegroups.com
Salut Philippe,

Mais pourquoi représenter la même donnée une seule fois en mémoire ?
Pour économiser du GC ou pour être sûr qu'au sein d'une même
transaction, il n'y a pas deux versions du même objet ? Mes souvenirs
d'Hibernate ne sont plus très frais mais ça me semblerait logique
qu'Hibernate renvoie des objets qui reflètent l'état de la transaction
en cours.

Pour se passer des transactions il suffit d'effectuer séquentiellement
toutes les opérations qui peuvent modifier les entités. Ça va très
vite si on ne fait aucune entrée-sortie au milieu de la phase de
traitement. C'est à cause des entrées-sorties bloquantes qu'il faut
des tripotées de threads.
>>> >> techos+un...@googlegroups.com.
>>> >> Pour envoyer un message à ce groupe, adressez un e-mail à
>>> >> tec...@googlegroups.com.
>>> >> Visitez ce groupe à l'adresse https://groups.google.com/group/techos .
>>> >> Pour plus d'options, visitez le site
>>> >> https://groups.google.com/d/optout .
>>> >
>>> >
>>> >
>>> > --
>>> > Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>>> > "techos".
>>> > Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>>> > concernant, envoyez un e-mail à l'adresse
>>> > techos+un...@googlegroups.com.
>>> > Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
>>> > tec...@googlegroups.com.
>>> > Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
>>> > Pour obtenir davantage d'options, consultez la page
>>> > https://groups.google.com/d/optout.
>>> >
>>> >
>>> > --
>>> > Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>>> > "techos".
>>> > Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>>> > concernant, envoyez un e-mail à l'adresse
>>> > techos+un...@googlegroups.com.
>>> > Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
>>> > tec...@googlegroups.com.
>>> > Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
>>> > Pour obtenir davantage d'options, consultez la page
>>> > https://groups.google.com/d/optout.
>>>
>>> --
>>> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>>> techos.
>>> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>>> concernant, envoyez un e-mail à l'adresse
>>> techos+un...@googlegroups.com.
>>> Pour envoyer un message à ce groupe, adressez un e-mail à
>>> tec...@googlegroups.com.
>>> Visitez ce groupe à l'adresse https://groups.google.com/group/techos .
>>> Pour plus d'options, visitez le site https://groups.google.com/d/optout .
>>
>>
>> --
>> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>> "techos".
>> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>> concernant, envoyez un e-mail à l'adresse
>> techos+un...@googlegroups.com.
>> Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
>> tec...@googlegroups.com.
>> Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
>> Pour obtenir davantage d'options, consultez la page
>> https://groups.google.com/d/optout.
>
>
>
>
> --
> Philippe Kernévez
>
>
>
> Directeur technique (Suisse),
> pker...@octo.com
> +41 79 888 33 32
>
> Retrouvez OCTO sur OCTO Talk : http://blog.octo.com
> OCTO Technology http://www.octo.com
>
> --
> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
> "techos".
> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
> concernant, envoyez un e-mail à l'adresse
> techos+un...@googlegroups.com.

Philippe Kernévez

unread,
Oct 5, 2016, 9:02:16 AM10/5/16
to techos
Salut Laurent, 

Pour le 2 : être certain qu'il y a une seule version en mémoire (le 1 et toujours bon à prendre, mais il suffit de savoir un peu coder pour éviter le grand n'importe quoi).
Dans hibernate, la session garde une référence (hard référence, c'est pour ça que ce n'est pas un cache) sur les objets chargés. Ce qui permet lors d'une requête ultérieure dans le même traitement de redonner la même référence.
Dans des 'gros' logiciel il est difficile de savoir qu'une autre partie de l'algo a déjà chargé l'objet (et l'a éventuellement modifié).
En mode immutable, on a (vu de ma fenêtre, mais je connais mal le sujet) le même problème si 2 parties de l'algo ne référence pas la même version de l'objet. Comment évite-t-on ça ?



2016-10-05 14:52 GMT+02:00 Laurent Caillette <laurent....@gmail.com>:
Salut Philippe,

Mais pourquoi représenter la même donnée une seule fois en mémoire ?
Pour économiser du GC ou pour être sûr qu'au sein d'une même
transaction, il n'y a pas deux versions du même objet ? Mes souvenirs
d'Hibernate ne sont plus très frais mais ça me semblerait logique
qu'Hibernate renvoie des objets qui reflètent l'état de la transaction
en cours.

Pour se passer des transactions il suffit d'effectuer séquentiellement
toutes les opérations qui peuvent modifier les entités. Ça va très
vite si on ne fait aucune entrée-sortie au milieu de la phase de
traitement. C'est à cause des entrées-sorties bloquantes qu'il faut
des tripotées de threads.


2016-10-05 14:02 GMT+02:00 Philippe Kernévez <pker...@octo.com>:
> Ce que je trouve très intéressant dans Hibernate (car évite beaucoup de
> bugs) c'est le cache de niveau 1 (qui porte mal son nom, ce n'est pas un
> cache). Il évite de représenter 2 fois en mémoire la même donnée.
> Comment gérez-vous ça dans vos approches ?
>
> a+
>
> 2016-10-05 8:33 GMT+02:00 Francois-Xavier Bonnet

>>> >> Pour envoyer un message à ce groupe, adressez un e-mail à
>>> >> tec...@googlegroups.com.
>>> >> Visitez ce groupe à l'adresse https://groups.google.com/group/techos .
>>> >> Pour plus d'options, visitez le site
>>> >> https://groups.google.com/d/optout .
>>> >
>>> >
>>> >
>>> > --
>>> > Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>>> > "techos".
>>> > Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>>> > concernant, envoyez un e-mail à l'adresse

>>> > Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
>>> > tec...@googlegroups.com.
>>> > Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
>>> > Pour obtenir davantage d'options, consultez la page
>>> > https://groups.google.com/d/optout.
>>> >
>>> >
>>> > --
>>> > Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>>> > "techos".
>>> > Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>>> > concernant, envoyez un e-mail à l'adresse

>>> > Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
>>> > tec...@googlegroups.com.
>>> > Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
>>> > Pour obtenir davantage d'options, consultez la page
>>> > https://groups.google.com/d/optout.
>>>
>>> --
>>> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>>> techos.
>>> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>>> concernant, envoyez un e-mail à l'adresse

>>> Pour envoyer un message à ce groupe, adressez un e-mail à
>>> tec...@googlegroups.com.
>>> Visitez ce groupe à l'adresse https://groups.google.com/group/techos .
>>> Pour plus d'options, visitez le site https://groups.google.com/d/optout .
>>
>>
>> --
>> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>> "techos".
>> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>> concernant, envoyez un e-mail à l'adresse

>> Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
>> tec...@googlegroups.com.
>> Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
>> Pour obtenir davantage d'options, consultez la page
>> https://groups.google.com/d/optout.
>
>
>
>
> --
> Philippe Kernévez
>
>
>
> Directeur technique (Suisse),
> pker...@octo.com
> +41 79 888 33 32
>
> Retrouvez OCTO sur OCTO Talk : http://blog.octo.com
> OCTO Technology http://www.octo.com
>
> --
> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
> "techos".
> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
> concernant, envoyez un e-mail à l'adresse

> Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
> tec...@googlegroups.com.
> Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
> Pour obtenir davantage d'options, consultez la page
> https://groups.google.com/d/optout.

--
Vous recevez ce message, car vous êtes abonné au groupe Google Groupes techos.
Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le concernant, envoyez un e-mail à l'adresse techos+unsubscribe@googlegroups.com.

Pour envoyer un message à ce groupe, adressez un e-mail à tec...@googlegroups.com.
Visitez ce groupe à l'adresse https://groups.google.com/group/techos .
Pour plus d'options, visitez le site https://groups.google.com/d/optout .



--

Henri Tremblay

unread,
Oct 5, 2016, 10:09:34 AM10/5/16
to tec...@googlegroups.com
C'est rigolo. Ça ressemble beaucoup à un DSL qu'on avait fait. Voir ici: http://blog.octo.com/un-dsl-sql-pour-java/

Et sans rapport, pour générer le modèle, jhipster utilise ça: https://jhipster.github.io/jdl-studio/


Julien

Julien Kirch

unread,
Oct 5, 2016, 10:34:33 AM10/5/16
to tec...@googlegroups.com
Il manque le typage ce qui est quand même une des idées structurante du POC.

Julien


Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le concernant, envoyez un e-mail à l'adresse techos+un...@googlegroups.com.
Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse tec...@googlegroups.com.

Laurent Caillette

unread,
Oct 5, 2016, 10:37:14 AM10/5/16
to tec...@googlegroups.com
Je pense que ta question vaut pour toutes les solutions qui ne
reproduisent pas la mécanique d'Hibernate.

Si la même entité est assez volumineuse pour être coupée en 2 morceaux
on peut faire comme si c'était 2 entités (autrement dit un arbre avec
2 faisceaux de branches dans 2 directions).

Si les 2 parties de l'algo sont interdépendantes, il faut d'un coup
tous les morceaux d'entités sur lesquels on va bosser.

La vraie question derrière c'est comment représenter les modifications
sur une grappe d'objets, de façon à les retransformer en requêtes de
mises à jour.

Si tu as des objets immuables, tu ne peux pas avoir de cycles. Tu ne
peux avoir que des arbres, qui sont des listes contenant d'autres
nœuds ou des types élémentaires (String, int, DateTime...) facilement
comparables. Donc tu peux aisément calculer la différence entre
l'objet original et l'objet modifié, surtout si les entités
proviennent de code généré par tes soins. (Une version évoluée
d'Immutables pourrait supporter cette comparaison "profonde".) Si tu
sais ce qui a changé, tu peux générer les requêtes pour mettre à jour
le SGBDR, en vérifiant les identifiants de version s'il y en a.

Si tu as besoin de faire un cycle, tu références un objet-clé qui
indique l'entité référencée. Tant que la clé ne change pas, on ne
touche pas à l'entité derrière, à moins qu'elle apparaisse
explicitement dans les objets modifiés.

Bon il y a un coût à toutes ces belles choses. Disons que tu as une
grappe d'objets où une instance de ``A`` référence des ``B`` qui
référencent des ``C``:

<<<
A -->* B -->* C
>>>

Si tu veux modifier un C tout seul, tu fais une copie avec
modification. Mais comment mettre à jour le ``B`` et le ``A`` pour que
le nouvel arbre reflète les modifications ?

Là il faut réfléchir un peu et céder à la tentation de générer un
cycle "en douce", au moment de l'instanciation par le code généré.
Parce que du coup il y aurait une rupture de contrat, une instance de
``C`` ne serait pas aussi indépendante du contexte qu'elle en a l'air.
Le truc, c'est d'expliciter le contexte. Par exemple si on veut
propager automatiquement le changement d'un ``C`` au ``A`` qui le
contient il faut une classe ``ABCPath`` qui explicite la relation
transitive. Ça s'utiliserait comme ça :

<<<
ABCPath modify( ABCPath path, int newValueForC ) {
return path.newC( path.actualC.updateC( newValueForC ) )
} ;
>>>

Je n'ai pas expérimenté cette approche avec des entités métier, mais
voici une famille d'algorithmes pour des arbres immuables qui se base
sur un objet ``Treepath`` grâce auquel on contextualise le nœud de
l'arbre sur lequel on opère la modification. Le code est là, il y a
des schémas pour aider la compréhension :
https://github.com/caillette/novelang/blob/master/modules/tree/src/main/java/org/novelang/common/tree/TreepathTools.java

On voit que chaque modification d'un nœud entraîne autant de
recréation d'objets nœuds qu'il y a de niveaux de profondeur dans le
``Treepath``.

Sur le projet où je travaille, il y a autant d'entités immuables que
possible, mais la modification par copie se fait à la main, parce que
ce n'est suffisamment compliqué pour justifier de la génération de
code. S'il y avait besoin, je regarderais ce qu'on peut faire à partir
des Builders d'Immutable.

Julien Kirch

unread,
Oct 5, 2016, 10:50:31 AM10/5/16
to tec...@googlegroups.com
Hello,

avec les idées sur l’immutabilité je ne sais pas si c’est un bon pattern ou pas :
- d’un côté c’est pratique
- d’un autre côté, l’approche « si tu as fais une modification pas encore persistée, et que si tu demander à rerécupérer l’objet tu retrouve la même instance avec les modifications en cours » est très très implicite, ça me gène que ça soit le comportement par défaut.

Je ne sais pas si je ne préfèrerai pas deux API :
- Une qui récupères l’objet "tel qu’il est en base", et qui te mets un warning / erreur s’il existe déjà dans la session dans un état modifié mais non persisté, ça évite d’avoir des bugs bizarres mais aussi les « forks » d’objets.
- Une qui récupère la version dans la session si elle existe, avec les modifications, et éventuellement pour les algo compliqués tu peux demander pour qu’il soit automatiquement persisté à la fin de la session.

Julien

Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le concernant, envoyez un e-mail à l'adresse techos+un...@googlegroups.com.

Laurent Caillette

unread,
Oct 5, 2016, 11:27:44 AM10/5/16
to tec...@googlegroups.com
J'ai l'impression qu'à un bout on a Hibernate, ses entrées-sorties
n'importe quand et son chargement différé, qui constitue un ensemble
cohérent. À l'autre bout avec les objets immuables à fond et les
entrées-sorties bien délimitées, c'est cohérent aussi bien
qu'expérimental. Entre les deux on ne peut trouver que des compromis
qui ne déboucheront pas sur un comportement homogène. J'aimais bien
l'idée du début, vérifier tout ce qu'on peut à la compilation,
maintenant il est question de comportements "surprise" à l'exécution.

Écrire une extension d'Hibernate pour requêter plus proprement, ça
pourrait préserver l'idée originale tout en s'appuyant sur des choix
bien compris.
>> > <francois-xa...@centraliens.net>:
>> >>> >> techos+un...@googlegroups.com.
>> >>> >> Pour envoyer un message à ce groupe, adressez un e-mail à
>> >>> >> tec...@googlegroups.com.
>> >>> >> Visitez ce groupe à l'adresse
>> >>> >> https://groups.google.com/group/techos .
>> >>> >> Pour plus d'options, visitez le site
>> >>> >> https://groups.google.com/d/optout .
>> >>> >
>> >>> >
>> >>> >
>> >>> > --
>> >>> > Vous recevez ce message, car vous êtes abonné au groupe Google
>> >>> > Groupes
>> >>> > "techos".
>> >>> > Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>> >>> > concernant, envoyez un e-mail à l'adresse
>> >>> > techos+un...@googlegroups.com.
>> >>> > Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
>> >>> > tec...@googlegroups.com.
>> >>> > Visitez ce groupe à l'adresse
>> >>> > https://groups.google.com/group/techos.
>> >>> > Pour obtenir davantage d'options, consultez la page
>> >>> > https://groups.google.com/d/optout.
>> >>> >
>> >>> >
>> >>> > --
>> >>> > Vous recevez ce message, car vous êtes abonné au groupe Google
>> >>> > Groupes
>> >>> > "techos".
>> >>> > Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>> >>> > concernant, envoyez un e-mail à l'adresse
>> >>> > techos+un...@googlegroups.com.
>> >>> > Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
>> >>> > tec...@googlegroups.com.
>> >>> > Visitez ce groupe à l'adresse
>> >>> > https://groups.google.com/group/techos.
>> >>> > Pour obtenir davantage d'options, consultez la page
>> >>> > https://groups.google.com/d/optout.
>> >>>
>> >>> --
>> >>> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>> >>> techos.
>> >>> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>> >>> concernant, envoyez un e-mail à l'adresse
>> >>> techos+un...@googlegroups.com.
>> >>> Pour envoyer un message à ce groupe, adressez un e-mail à
>> >>> tec...@googlegroups.com.
>> >>> Visitez ce groupe à l'adresse https://groups.google.com/group/techos .
>> >>> Pour plus d'options, visitez le site
>> >>> https://groups.google.com/d/optout .
>> >>
>> >>
>> >> --
>> >> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>> >> "techos".
>> >> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>> >> concernant, envoyez un e-mail à l'adresse
>> >> techos+un...@googlegroups.com.
>> >> Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
>> >> tec...@googlegroups.com.
>> >> Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
>> >> Pour obtenir davantage d'options, consultez la page
>> >> https://groups.google.com/d/optout.
>> >
>> >
>> >
>> >
>> > --
>> > Philippe Kernévez
>> >
>> >
>> >
>> > Directeur technique (Suisse),
>> > pker...@octo.com
>> > +41 79 888 33 32
>> >
>> > Retrouvez OCTO sur OCTO Talk : http://blog.octo.com
>> > OCTO Technology http://www.octo.com
>> >
>> > --
>> > Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>> > "techos".
>> > Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>> > concernant, envoyez un e-mail à l'adresse
>> > techos+un...@googlegroups.com.
>> > Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
>> > tec...@googlegroups.com.
>> > Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
>> > Pour obtenir davantage d'options, consultez la page
>> > https://groups.google.com/d/optout.
>>
>> --
>> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>> techos.
>> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>> concernant, envoyez un e-mail à l'adresse

Philippe Kernévez

unread,
Oct 5, 2016, 12:41:09 PM10/5/16
to techos
C'est vrai que le POC pourrait n'être qu'une API Criteria plus robuste/efficace que celle fournit, mais je ne suis pas certain que c'était la motivation de Julien :-)


>> >>> >> Pour envoyer un message à ce groupe, adressez un e-mail à
>> >>> >> tec...@googlegroups.com.
>> >>> >> Visitez ce groupe à l'adresse
>> >>> >> https://groups.google.com/group/techos .
>> >>> >> Pour plus d'options, visitez le site
>> >>> >> https://groups.google.com/d/optout .
>> >>> >
>> >>> >
>> >>> >
>> >>> > --
>> >>> > Vous recevez ce message, car vous êtes abonné au groupe Google
>> >>> > Groupes
>> >>> > "techos".
>> >>> > Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>> >>> > concernant, envoyez un e-mail à l'adresse

>> >>> > Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
>> >>> > tec...@googlegroups.com.
>> >>> > Visitez ce groupe à l'adresse
>> >>> > https://groups.google.com/group/techos.
>> >>> > Pour obtenir davantage d'options, consultez la page
>> >>> > https://groups.google.com/d/optout.
>> >>> >
>> >>> >
>> >>> > --
>> >>> > Vous recevez ce message, car vous êtes abonné au groupe Google
>> >>> > Groupes
>> >>> > "techos".
>> >>> > Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>> >>> > concernant, envoyez un e-mail à l'adresse

>> >>> > Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
>> >>> > tec...@googlegroups.com.
>> >>> > Visitez ce groupe à l'adresse
>> >>> > https://groups.google.com/group/techos.
>> >>> > Pour obtenir davantage d'options, consultez la page
>> >>> > https://groups.google.com/d/optout.
>> >>>
>> >>> --
>> >>> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>> >>> techos.
>> >>> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>> >>> concernant, envoyez un e-mail à l'adresse

>> >>> Pour envoyer un message à ce groupe, adressez un e-mail à
>> >>> tec...@googlegroups.com.
>> >>> Visitez ce groupe à l'adresse https://groups.google.com/group/techos .
>> >>> Pour plus d'options, visitez le site
>> >>> https://groups.google.com/d/optout .
>> >>
>> >>
>> >> --
>> >> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>> >> "techos".
>> >> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>> >> concernant, envoyez un e-mail à l'adresse

>> >> Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
>> >> tec...@googlegroups.com.
>> >> Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
>> >> Pour obtenir davantage d'options, consultez la page
>> >> https://groups.google.com/d/optout.
>> >
>> >
>> >
>> >
>> > --
>> > Philippe Kernévez
>> >
>> >
>> >
>> > Directeur technique (Suisse),
>> > pker...@octo.com
>> > +41 79 888 33 32
>> >
>> > Retrouvez OCTO sur OCTO Talk : http://blog.octo.com
>> > OCTO Technology http://www.octo.com
>> >
>> > --
>> > Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>> > "techos".
>> > Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>> > concernant, envoyez un e-mail à l'adresse

>> > Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
>> > tec...@googlegroups.com.
>> > Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
>> > Pour obtenir davantage d'options, consultez la page
>> > https://groups.google.com/d/optout.
>>
>> --
>> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
>> techos.
>> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
>> concernant, envoyez un e-mail à l'adresse

>> Pour envoyer un message à ce groupe, adressez un e-mail à
>> tec...@googlegroups.com.
>> Visitez ce groupe à l'adresse https://groups.google.com/group/techos .
>> Pour plus d'options, visitez le site https://groups.google.com/d/optout .
>
>
>
>
> --
> Philippe Kernévez
>
>
>
> Directeur technique (Suisse),
> pker...@octo.com
> +41 79 888 33 32
>
> Retrouvez OCTO sur OCTO Talk : http://blog.octo.com
> OCTO Technology http://www.octo.com
>
> --
> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
> "techos".
> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
> concernant, envoyez un e-mail à l'adresse

> Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
> tec...@googlegroups.com.
> Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
> Pour obtenir davantage d'options, consultez la page
> https://groups.google.com/d/optout.
>
>
> --
> Vous recevez ce message, car vous êtes abonné au groupe Google Groupes
> "techos".
> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le
> concernant, envoyez un e-mail à l'adresse

> Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse
> tec...@googlegroups.com.
> Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
> Pour obtenir davantage d'options, consultez la page
> https://groups.google.com/d/optout.

--
Vous recevez ce message, car vous êtes abonné au groupe Google Groupes techos.
Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le concernant, envoyez un e-mail à l'adresse techos+unsubscribe@googlegroups.com.

Pour envoyer un message à ce groupe, adressez un e-mail à tec...@googlegroups.com.
Visitez ce groupe à l'adresse https://groups.google.com/group/techos .
Pour plus d'options, visitez le site https://groups.google.com/d/optout .

Julien Kirch

unread,
Oct 5, 2016, 2:31:24 PM10/5/16
to tec...@googlegroups.com
Hum,
- pour le POC je voulais aussi voir à quel point la génération du SQL était compliquée, et me passer d’hibernate
- j’ai peur de l'impédance entre les deux : déjà que les API d’hibernate sont pas très cohérentes entre elles, bâtir dessus une autre abstraction ça me fait peur

CEu4qQtWYAImSBf.jpg-large.jpg


Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le concernant, envoyez un e-mail à l'adresse techos+un...@googlegroups.com.

Sylvain Rey

unread,
Oct 6, 2016, 7:05:54 AM10/6/16
to tec...@googlegroups.com
Hello

Si tu veux t'inspirer de ce qui se fait dans le monde .Net, le mot clé important est "Micro ORM".
Les plus connus (Massive, PetaPOCO, Dapper, Subsonic...) partent souvent d'une implémentation minimaliste (Massive est originellement une single-file class de 400 lignes), souvent grâce à des aspects du langage/de la VM super intéressants (utilisation du mot clé dynamic qui fait le lien entre le typage dynamique et les objets finaux avec typage statique, génération dynamiques de méthodes (bytecode generation) pour à la fois la souplesse d'accès aux propriétés des objets et la performance, appels aynchrones, etc.)

Ils expriment les query en SQL :
Avantages : ils restent des Micro-ORM faciles à coder et à comprendre, on réexploite la puissance du SQL (point évoqué par François-Xavier), l'ORM impedance mismatch est "contenu" à cet endroit et il est géré par le développeur qui exprime les relations lui-même dans son SQL
Inconvénients : cette partie n'est pas statiquement typée, ils n'expriment pas les relations

Aucun ne formalise le mapping sous forme XML ; soient ils infèrent (typage dynamique), soient ils se contentent du POCO (typage statique)
=> mon feedback perso et biaisé : XML est un show stopper dans ton projet

Maintenant, si tu te penches vers l'aspect "meilleure API Hibernate que Criteria avec typage statique, tu peux regarder ce qui se fait dans NHibernate avec QueryOver (dont je suis plutôt fan).
Je suis quasiment sûr qu'Hibernate n'a pas d'équivalent à QueryOver... donc, oui, il y a du taf pour les javaistes de ce côté !
=> mon feedback perso et biaisé :même si tu ne fais pas une API pour Hibernate, je t'enjoins vraiment à regarder QueryOver... je trouve que c'est beaucoup plus élégant que ce tu proposes pour l'instant dans ton POC (maintenant qu'on a les Lambda Expressions et les Streams en Java, on peut faire de belles choses !)

--------

Arrivé à ce stade de ma réflexion, je m'aperçois que je ne connais pas jOOQ, mentionné par Laurent... j'y jette un oeil superficiel et... c'est quoi le problème avec jOOQ ? c'est pas exactement ce que tu veux... en mieux :-D ?
Sauf si tu sais d'avance que tu vas faire beaucoup de relationnel et que tu veux une gestion de graphe...Hibernate restera probablement un incontournable.. lui manquant plus que l'équivalent à QueryOver !

++
Sylvain


Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le concernant, envoyez un e-mail à l'adresse techos+unsubscribe@googlegroups.com.

Pour envoyer un message à ce groupe, envoyez un e-mail à l'adresse tec...@googlegroups.com.
Visitez ce groupe à l'adresse https://groups.google.com/group/techos.
Pour obtenir davantage d'options, consultez la page https://groups.google.com/d/optout.

Loïc Lefèvre

unread,
Oct 6, 2016, 7:40:59 AM10/6/16
to tec...@googlegroups.com

Hello
sympa la discussion :)
Je lance une idée comme cela et je vois ce que cela donne (sans réfléchir aux impacts que vous saurez vite detecter) :

Et si le point était plutôt de coder en SQL *puis* de générer du code Java, .Net, HyperQ, Whatever... par rapport au code SQL, à sa richesse, à sa densité (y compris pourquoi pas le mcd/mpd)

:)
Bonne après midi et bonne réflexions
Loïc

Julien Kirch

unread,
Oct 6, 2016, 8:11:14 AM10/6/16
to tec...@googlegroups.com
Le 6 oct. 2016 à 13:05, Sylvain Rey <sylva...@gmail.com> a écrit :

Hello

Si tu veux t'inspirer de ce qui se fait dans le monde .Net, le mot clé important est "Micro ORM".
Les plus connus (Massive, PetaPOCO, Dapper, Subsonic...) partent souvent d'une implémentation minimaliste (Massive est originellement une single-file class de 400 lignes), souvent grâce à des aspects du langage/de la VM super intéressants (utilisation du mot clé dynamic qui fait le lien entre le typage dynamique et les objets finaux avec typage statique, génération dynamiques de méthodes (bytecode generation) pour à la fois la souplesse d'accès aux propriétés des objets et la performance, appels aynchrones, etc.)

Ils expriment les query en SQL :
Avantages : ils restent des Micro-ORM faciles à coder et à comprendre, on réexploite la puissance du SQL (point évoqué par François-Xavier), l'ORM impedance mismatch est "contenu" à cet endroit et il est géré par le développeur qui exprime les relations lui-même dans son SQL
Inconvénients : cette partie n'est pas statiquement typée, ils n'expriment pas les relations

Donc c’est l’inverse de ce que je veux :-p.
Si je voulais un truc dynamique je m’inspirerai probablement plutôt de Sequel https://github.com/jeremyevans/sequel qui est un ORM ruby minimaliste « hybride » (tu peux mélanger SQL et ORM).


Maintenant, si tu te penches vers l'aspect "meilleure API Hibernate que Criteria avec typage statique, tu peux regarder ce qui se fait dans NHibernate avec QueryOver (dont je suis plutôt fan).
Je suis quasiment sûr qu'Hibernate n'a pas d'équivalent à QueryOver... donc, oui, il y a du taf pour les javaistes de ce côté !
=> mon feedback perso et biaisé :même si tu ne fais pas une API pour Hibernate, je t'enjoins vraiment à regarder QueryOver... je trouve que c'est beaucoup plus élégant que ce tu proposes pour l'instant dans ton POC (maintenant qu'on a les Lambda Expressions et les Streams en Java, on peut faire de belles choses !)

C’est intéressant, je ne sais pas si tu peux faire aussi bien en Java car les API d’introspection très limitées malheureusement.

Arrivé à ce stade de ma réflexion, je m'aperçois que je ne connais pas jOOQ, mentionné par Laurent... j'y jette un oeil superficiel et... c'est quoi le problème avec jOOQ ? c'est pas exactement ce que tu veux... en mieux :-D ?
Sauf si tu sais d'avance que tu vas faire beaucoup de relationnel et que tu veux une gestion de graphe...Hibernate restera probablement un incontournable.. lui manquant plus que l'équivalent à QueryOver !

Ça ressemble, j’étais tombé dessus en regardant ce qui se faisait, mais :
- Ils génèrent à partir de la base, avoir un fichier comme source c’est bien plus pratique.
- C’est du open-core

Julien
Reply all
Reply to author
Forward
0 new messages