Nous avions vu dans un précédent article quelques livres dont j'avais agréablement parcouru les pages [1]. J'avais eu réellement un coup de cœur pour le livre de Aurelien Géron "Machine Learning avec Scikit-Learn" et ce à tel point que j'ai poursuivi sur la suite "Deep Learning avec Tensor Flow".
Honnêtement, je vais pas tourner au tour du pot, ce livre est une référence. Pour avoir suivi un cursus d’ingénieur en automatique et en informatique et avoir eut un cours sur les réseaux de neurones, je voulais d'une part, me rafraîchir la mémoire et me mettre à jour, et bien banco!
Tout y es avec toutes les références qu'il faut pour poursuivre l'aventure (les publications scientifiques ça n'a pas de prix!)
Alors il est clair que ce n'est pas un sujet simple, quelques bonnes bases en mathématique (algèbre linéaire) sont nécessaires et aussi un peu en programmation (et encore le framework Tensor Flow est assez clair dans les principes).
Remarquez qu'il est plus simple d'aborder ce livre en lisant le premier volume sur le Machine Learning. Ce premier permet de donner de bonnes bases dans les démarches de traitement des données et sur les procédures d'apprentissages afin d'en appréhender un peu le lexique.
Au delà de cela, les grandes familles de réseaux neuronaux sont décrites, les types de fonctions d'activations, les méthodes d'apprentissages, leurs architectures... avec pour ma part une petite préférence pour les réseaux de Hopfield et de Markov car quelques part, ils me font penser aux SED (allez savoir pourquoi...)
La constitution d’un plugin maven est souvent une étape indispensable pour régler certaines problématiques de nos projets.
En réaliser un n’est pas très compliqué, cela consiste essentiellement en un packaging maven un peu particulier et en la définition d’un point d’entrée spécifique, le MOJO.
Le packaging doit être de type maven-plugin, il suffit donc de créer un projet maven classique intégrant ce packaging [1]. Attention par contre car le nommage de votre plugin conditionnera la commande qu’il faudra employer pour l’appeler.
Ainsi, imaginons un plugin maven permettant d’extraire les données du projet (issu du pom, on va pas faire trop compliqué….).
Ainsi on va définir notre projet maven comme suit:
Et c’est tout pour avoir une build par défaut sachant que pour appeler notre plugin pendant un build il faudra appliquer soit le nom complet du plugin :
Le nom court étant la simple réduction du nom de l’artifactId sans son extends maven-plugin.
Alors dans cette commande on identifie le mot clef extract. Ce mot clef est le goal de notre plugin (bien sur on peut en définir plusieurs). Ce goal, nous allons le voir est directement relié à notre Mojo, maven se chargeant lorsque l’on appelle ce goal d'exécuter le Mojo associé.
Voyons donc comment construire ce Mojo. Un Mojo [6] est un simple Pojo avec un M pour Maven. Ce Mojo va hériter de la classe AbstractMojo nous incitant à définir la classe exécute qui servira de point d’entrée pour l'exécution du plugin pour le goal associé. Pour définir justement le goal associé, on va s’appuyer l’annotation @Mojo et utiliser @Component pour injecter le projet maven dans notre mojo (en profitant des fonctionnalités d’injection de dépendance de plexus, nous y reviendrons).
A noter qu’il existe les annotations @Execute permettant de forker l'exécution du build maven et @Parameter qui permet de rendre d’injecter des données au plugin au moment de son appel (soit via -DmonParam soit directement dans le pom sous la forme de valeur de configuration) mais on entrera pas dans les détails de ces dernières.
Voila, à l'exécution de la commande maven, le mojo sera exécuté générant un fichier csv dans lequel il déposera pour chaque projet les caractéristiques de celui ci. Ca ne fait pas grand chose mais ca mange pas de pain. A bientôt et pour ceux qui voudront d’autres références, elles sont ici:
Nous avions parlé des annotations lorsque nous avions traité des evolutions Java 5 [1]. Aujourd’hui je vous propose de nous intéresser à en faire quelques unes et de réaliser le ou les processeurs d’annotations qui vont bien pour les traiter dans les contextes de la compilation puis at runtime. Nous tâcherons de traiter les différents cas que nous pourrions rencontrer en définissant des annotations sur des cibles variées.
Tout d’abord, premier point, une annotation c’est quoi? Alors pour présenter cela, je vais reprendre l’exemple que nous avions vu alors dans [1]:
@SuppressWarnings(value = { "static-method" })
public <S> S functionGen(S s)
{
return s;
}
Comme vous l’avez deviné, l’annotation ici est évidemment SuppressWarnings. Elle est identifiable via un @ et prend ici une liste de paramètres réduits à un seul. Le détail nous importe peu, ce qui est intéressant est l’information que l’annotation va transporter.
Une annotation a un nom, potentiellement des variables, et si ce n’est pas visible ici, possèdes des informations sur son contexte d’utilisation, et de sa porté. Ces deux derniers éléments sont positionnés lors de la définition de l’annotation.
Son contexte d’utilisation permet de préciser où celle ci pourra être utilisé. Ici dans l’exemple, l’annotation est positionné sur la méthode mais celle-ci peut être positionné sur une classe, une donnée membre ou même un package, tous ces éléments n'étant pas en exclusion mutuelle. Potentiellement une même annotation peut être utilisé sur plusieurs type de structures différentes.
De façon complète, il est possible de positionner une annotation (sauf définit contrairement) sur:
les classes
les méthodes
les interfaces
les variables
les packages
Au delà du cadre de son utilisation, la porté a par contre plus d’importance sur l’utilisation fonctionnelle de l’annotation. En effet, la porté définit à quel moment l’annotation doit être traitée comme une information utile au programme java. Celle ci peut être
soit intégré qu’au code source (et ne sera plus visible ensuite, on utilise ce type d’annotation essentiellement pour donner de l’information au développeur ou aux analyseurs de code),
soit intégré au code binaire (permettant au compilateur de réaliser des tâches supplémentaires)
soit intégrée pour être traité au runtime afin de permettre d'adjonction de comportement dynamique (beaucoup de framework utilise aujourd’hui ce type d’API)
Bien sur ces différents cas d’utilisation des annotations nécessitent des mécanismes et des traitements différents. Autant on comprend vite comment va fonctionner les traitements d’annotations associé au code source qui auront surtout un rôle informatif. Par contre lorsque les annotations sont traité lors de la phase de compilation, il sera nécessaire d’utiliser le mécanisme des processeurs d’annotations (l’API Pluggable Annotation Processor). Enfin lorsque celles ci sont définies pour être traité at runtime, il faudra user de l’introspection et de certains type de pattern afin de profiter intelligemment des possibilités des annotations.
Mais avant de pouvoir traiter les annotations, je vous propose de reprendre tous ces points en définissant notre propre annotation.
Prenons d’abord l’exemple d’une annotation fournissant un lien documentaire explicitant la référence fonctionnelle ou technique amont. Pour la définir, nous allons créer une pseudo interface en utilisant le caractère @ avant le mot clef interface. Nous y déclarent une propriété document et une propriété chapitre et une propriété page.
Ces informations portent le côté fonctionnel, il faut maintenant définir le côté technique pour spécifier le contexte d’utilisation et de traitement.
Pour cela il faut utiliser des méta-annotations (une annotation pour annotations):
@Documented : Permet de préciser la présence de l’annotation dans la java doc de l'élément portant celle ci
@Inherit : Permet de transmettre les propriétés informationnelles de l’annotation aux classes filles de l'élément portant celle-ci
@Retention : précise le niveau de traitement (source, bitcode ou runtime)
@Target: précise la ou les cibles sur lesquels l’annotation peut être apposé.
Dans le contexte très basique de cet exemple, nous nous limiterons a simplement a specifier la Retention a source.
@Retention(RetentionPolicy.SOURCE)
public @interface Reference
{
String document();
String chapitre();
int page();
}
Voilà dans nos classes nous pourrons alors par exemple préciser à quelle partie de la documentation de la spécification cliente les classes font références:
@Reference(document=”docAmont.doc”,chapitre=”Service Client”,page=44)
public class ServiceClient
{
public void service();
}
Imaginons maintenant une nouvelle annotation nécessitant un traitement plus profond à effectuer lors de la compilation. Par exemple une annotation @Relation qui associe a une classe permettra la production d’une table du même nom dans une bd (pour faire simple, nous passerons sur la production des attributs de relation mais on imagine facilement qu’il faudrait faire une annotation pour les données membres de la classe)
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Relation
{
}
Ainsi, sur une classe de notre application, nous placerons tout simplement notre annotation ainsi:
@Relation
public class HelloWorld
{
}
Alors bien sur si ceci nous permet de sémantiquement faire un lien entre notre classe et la relation d’une BD, il nous faut procéder à la mise en oeuvre d’un Processeur d’Annotation dont le but sera de récupérer toutes les classes postant celle-ci et exécutera un CREATE TABLE en sql sur la BD en question.
Pour réaliser un processeur d’annotation, il n’y a rien de complexe, il suffit de produire un simple artifact maven de type jar dans lequel nous trouverons:
une classe RelationProcessor dérivant la classe AbstractProcessor portant les annotations SupportedAnnotationTypes (précisant quelles annotations nous traitons) et SupportedSourceVersion (précisant dans quel version java la compilation aura lieu)
un fichier de conf dans le répertoire META-INF/services nommé javax.annotation.processing.Processor et contenant le nom complet de notre classe processor.
A noter qu’il sera nécessaire de désactiver l’utilisation des processeurs d’annotations pour le packaging de notre propre processeur (sinon celui ci tentera se traiter lui même), il s’agit de l’option de configuration -proc:none du maven-compiler-plugin. Ensuite il suffira de faire une dépendance à la compilation pour que notre logiciel, lors de sa compile trouve par inspection le fichier de conf de notre jar contenant le processeur et l'exécute.
Voyons comment cela se presente:
Tout d’abord nous allons definir un pom module pour plus de simplicité:
tc-annotationtc-processortc-helloworld
Le premier module contient nos annotation, le second contiendra le processeur et le troisieme sera notre classe helloworld
Le module d’annotation n’a rien de special, c’est un projet maven classique produisant un jar (donc l’utilisation du plugin maven-compiler-plugin est largement suffisant) et contenant la définition de notre annotation:
Le module processor est un peu plus complexe, comme nous l’avons vu il faut definir une classe heritant de la classe AbstractProcessor. Cette classe va recuperer l’ensemble des elements portant notre annotation et envera une requete en BD pour creer la table associée.
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({ "annot.Relation" })
public class HWProcessor extends AbstractProcessor {
public static final String JDBC_DRIVER = "org.postgresql.Driver";
public static final String DB_URL = "jdbc:postgresql://localhost:5432/test";
public static final String USER = "postgres";
public static final String PASS = "";
public static String CREATE="CREATE TABLE ";
@Override
public boolean process(Set arg0, RoundEnvironment arg1) {
System.out.println("HWProcessor running");
Connection conn =null;
try {
Class.forName(JDBC_DRIVER);
conn = this.getConnection();
for (Element e : arg1.getRootElements()) {
if (e.getAnnotation(Relation.class) != null) {
this.createRelation(e,conn);
}
}
} catch ( SQLException | ClassNotFoundException e1) {
System.out.println(e1);
return false;
}
return true;
}
public void createRelation(Element e,Connection conn) throws SQLException
{
PreparedStatement statement=conn.prepareStatement(this.CREATE+ e.getSimpleName().toString()+"();");
System.out.println("Mon element : "+e.getSimpleName().toString());
statement.executeQuery();
}
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(DB_URL, USER, PASS);
}
}
a noter de ne pas oublier d’ajouter le fichier javax.annotation.processing.Processor
au niveau du build maven, il vous faudra ajouter la ligne -proc:none a la configuration du plugin maven-compiler-plugin. Ne pas oublié non plus de bien sur ajouter les dependances a l’artifact contenant l’annotation et ici en plus a postgres puisqu’il s’agit de la BD que nous utilisons.
Enfin il reste a utiliser notre annotation dans notre projet. Donc evidement on depose l’annotation sur la classe:
import annot.Relation;
@Relation
public class HelloWorld {}
Enfin il faudra bien sur configurer notre build. Alors soit on precise dans le plugin maven-compiler-plugin que l’on va utiliser un artifact contenant un processeur d’annotation en eventuellement precisant le processeur :
Mais cela, on peut aussi se reposer sur la capacité du jdk a decouvrir le processeur et ne declarer l’artifact que comme une dependance de compilation et ne pas mettre les champs annotationProcessors et annotationProcessorPaths (simplifiant largement la complexité du pom au passage). on prefere donc:
HWProcessor running
Mon element : HelloWorld
org.postgresql.util.PSQLException: Aucun résultat retourné par la requête.
Ok donc ca a l’air de s’etre bien passé vérifions la BD:
psql -U postgres -w -d test -c "SELECT table_name from information_schema.tables where table_name='helloworld';"
table_name
------------
helloworld
(1 ligne)
Bonne nouvelle notre table est la dans le schéma!!
Bien voila, nous venons de passer en revu les deux ca d’utilisation que sont les annotations sur les sources et sur les classes. Leur objectifs ne sont clairement pas les même et si le premier cas ne nécessite pas beaucoup de travail, on peut voir qu’il faudra en fournir un peu plus sur le second en élaborant des processeur d’annotations.
Maintenant il reste un dernier cas d’utilisation, les annotations utilisés at runtime. Cependant désolé mais je ne traiterai pas de leur utilisation dans cet article. En effet, d’une part car cela relève globalement de la même approche introspective que pour la conception d’un processeur mais en employant plutôt quelques patterns bien senti comme l’Invocation Handler. Ce dernier est d’ailleur l’objet d’un prochain article et nous en profiterons alors pour jouer avec les dites annotations at runtime et nous verrons que le gros de travail sur le sujet a globalement été réalisé ici.
Aujourd’hui nous revenons à java et plus particulièrement à sa version entreprise JEE. Le but de cet article n’est pas de présenter JEE dans son ensemble ( cela sera le propos d’un article plus générale sur JEE) mais de regarder un pattern important du développement des applications d’entreprises. Le pattern que j'évoque n’est pas le pattern MVC auquel nous allons dédié un article plus tard également mais le pattern DAO (Data Access Object) [1] qui nous permet de gérer la couche d'accès aux bases de données. Bien sur aujourd’hui JPA (TopLink [2], Hibernate [3]) a largement pris sa place mais à l'époque où ce dernier n’avait pas la maturité qu’on lui connaît aujourd’hui, ce pattern a rendu bien des services techniques mais aussi théorique en apportant un concept clef de la programmation, le découplage.
Je vous propose donc de traiter un peu ce sujet afin et de le poursuivre par JPA (ba oui forcément…)
Bien sur j’aurai du commencer par une présentation générale de JEE avant d’aborder des sujet plus précis. Ainsi afin que l’on puisse s’y retrouver, il faut juste imaginer une architecture applicative JEE comme une architecture en 3 couches, une couche présentation, une couche métier et une couche données
Dans cette architecture, on trouvera un certains nombres de patterns permettant de faciliter le développement mais aussi permettant de rendre standard toutes applications JEE. Ainsi un développeur sur un projet JEE trouvera les mêmes types de structures dans un autre projet quelques soit le métier. Ainsi par exemple, on utilise classiquement le modèle MVC pour implémenter la couche présentation et gérer une partie de la couche métier. De la même manière, une application ayant des besoins de stockage de données et utilisant un SGBD devra implémenter une couche d'accès aux données. C’est à ce niveau que le pattern DAO va intervenir.
Le découplage de la couche métier
Dans l’absolu, lorsque l’on conçoit une application logiciel, la première composante à laquelle on pense et la composante métier . Effectivement, celle-ci comporte la plus grande partie de la valeur du logiciel en fournissant les briques fonctionnelles de base qui répondront aux besoins du client.
Cependant si le besoin fonctionnel nécessite la consolidation des données dans l’objectif d'être exploité ultérieurement, une première approche est mapper les données métiers directement à un système de stockage.
Par exemple si nous considérons un logiciel de gestion de tâches, associé à des utilisateurs qui vont les réaliser. nous pourrions avoir une structure métier tel que (je vous passe les accesseurs par soucis de concision mais ils doivent être présent afin de respecter la convention bean [11]):
Ainsi une première approche peut nous décider que sérialiser nos objets en leur ajoutant une fonction toXml() sera suffisant. On imagine rapidement les limitations de cette approche en terme de maintenance car cela implique de produire du code métier dépendant d’une solution technique de sauvegarde. Alors que se passera t il si l’on souhaite passer à l'échelle et utiliser une techno comme JDBC ? On va encore modifier le code métier afin d’y ajouter les spécificités techniques de cette techno et de la base de données qui sera choisie? Non bien sur et je ne vous parle pas des tests unitaires qu’il faudra réadapter à chaque fois…. et la complexité de les produire….
Il nous faut donc une approche rationnelle et propre permettant de développer séparément le code métier et la ou les solutions de stockage. Alors bien sur il existe des patterns génériques permettant le découplage de composantes logicielles comme les patterns visiteur[4] , proxy[5] , observateur[6] ou médiateur [7]. En fait le pattern DAO est plutôt une association de plusieurs patterns dont le Pont [8] et le stratégie [9].
Ainsi le principe est simple, le pattern DAO implique la définition d’un certain nombre d’interfaces fournissant les services pour la manipulation du modèle de données au travers de quatre principales procédures formant le CRUD [10] : Create, Read, Update, Delete.
Il nous faut donc une interface dédiée par élément métier:
L’utilisation des objets DAO au travers des interfaces va alors nous garantir l'indépendance au niveau implémentation. Il nous reste par contre à fournir un moyen d’obtenir ces objets. Pour cela, on va s’appuyer sur le pattern factory qui va au passage nous servir d’environnement pour toute la configuration des drivers de la base de données cible.
On aura donc une factory par famille d’objet implémentant les interfaces DAO. Par exemple:
Si on voulait faire ça bien, on réalisera également une interface pour la factory de façon à la découpler totalement de son utilisateur final (qui sera probablement une servlet … mais c’est une autre histoire)
Bien notre factory va nous produire les Objets UserDAOImpl, TaskDAOImpl dont l'implémentation est spécifique à la base de données gérée par la factory. On trouvera donc dans ces deux classes les composantes CRUD telles que:
Bien sur les composantes create, read, update et delete ne sont pas encore ici implémenté. On va s’en charger en utilisant la factory passé en paramètre de l’objet lors de sa construction. Par contre, et parce que c’est à la factory que nous avons donné le rôle de gérer les accès à la BD, nous allons ajouter des fonctions a cette première pour que les implémentations de DAO n’en soit que plus simple.
Pour implémenter ces méthodes, nous allons utiliser utiliser la méthode getConnection de la factory. Celle-ci va nous fournir un objet Connexion nous permettant de construire un Statement ou un PreparedStatement [12].
La différence entre les deux ces deux types d’objet est simple, les Statements permettant l'exécution d’une requête construite à la main alors que le PrepareStatement fournir une structure a trou a la place.
Dans tous les cas, leur exécution nous retournera un ResultSet contenant l’ensemble des données résultant de la requête.
Ainsi par exemple pour implémenter le read de la classe UserDAOImpl permettant de récupérer un utilisateur, on procédera tout d’abord en définissant la requête Sql a base select qui correspond (très simple dans cet exemple). Ensuite on demandera à la factory un PreparedStatement (obtenu à partir de la Connexion) que l’on exécutera. Son résultat sera alors traité afin d'être mapper avec un vrai objet métier User.
privatestatic String SELECT="select name from User";@Overridepublic User read(String name)throws SQLException {
PreparedStatement statement=this.factory.preparedStatement(SELECT);
ResultSet result=statement.executeQuery();returnthis.mapResultSetOnUser(result);}private User mapResultSetOnUser(ResultSet set)throws SQLException {
ser user=new User(set.getString("name"));return user;}
Bien sur ce cas présent est très simple et ne présente vraiment aucun difficulté particulière. Par contre, les méthodes traitant de la création, de la mise à jour et de la suppression vont être pour leur part un peu plus complexe à mener à cause de la nécessité de gérer la cohérence de la base de données.
En effet, la modification d’une association entre deux objets métiers va forcément impacter leur relation images dans le monde relationnel de la base de données. Et même avec tous les effort du monde, si l’on souhaite faire les choses de façon cohérente et ne pas risquer de tomber dans des états intermédiaire et passablement dégradé, il sera important d’employer un modèle transactionnel de plus haut niveau tel que JTA [13] mais c’est une autre histoire.
De plus rappelons nous, si a l’origine nous voulions découpler les couche métier et base de données, nous nous retrouvons malgré tout maintenant avec une bonne dose de code à maintenir (c’est toujours ça que de l’avoir dans le code métier). Dans un prochain article nous nous intéresserons a comment les frameworks de mapping objet relationnel ont résolu ce problème
Voilà, sans forcément être entré dans beaucoup de détails, nous avons fait un tour rapide du pattern DAO, de ses avantages et de ses faiblesses. Bien sur aujourd’hui d’autres approches sont à envisager pour concevoir la couche d'accès à la base de données mais il ne faut pas oublier qu’il existe encore des logiciels à maintenir utilisant encore cette technologie et qu’il reste bon de la connaître (du moins savoir de quoi nous sommes parti avant JPA, toplink, hibernate, etc....)