Thématiques principales

mercredi 28 mars 2018

Architectures types

Lorsque nous nous sommes intéressé au pattern DAO [1], nous avons pu voir que les systèmes logiciels se conçoivent en définissant une architecture répondant au mieux aux besoins clients en usant de différentes stratégies en terme de solutions techniques [2] (les patterns de conceptions, les frameworks, etc…) et de méthodes [3] (IDM, Agilité, Scrum, etc...).

Ainsi, au fils des articles nous avons croisé un certain nombre de patterns ou de framework qui clairement nous simplifie la vie pour produire notre application. Cependant ce genre de concepts se focalisent exclusivement sur le logiciel lui même sans fournir de réponses à des besoins environnementaux qui peuvent être nombreux et contraignants

Ainsi par exemple, que faire lorsque le logiciel doit être capable de gérer un taux de connexion élevé? Comment garantir la monté en charge? Comment s’assurer de l'intégrité des données ? de leur disponibilités ? de leur sauvegarde? Comment garantir qu’il n’y aura pas d'interruption de service? A toutes ces questions, le logiciel seul ne peut y répondre en plus du besoin pour lequel il est produit. En effet, si techniquement on peut imaginer répondre à ces problématiques, cela nécessiterait des développement spécifiques très lourd en plus du métier pour lequel le logiciel doit être fait et comme nous l’avons vu précédemment, toutes les approches purement logiciel sont beaucoup trop auto-centré.

A cette problématique existe heureusement un solution et celle-ci relève en fait du bon sens. Ainsi, si un logiciel doit être conçu et architecturé, son environnement doit l'être aussi! Et les choix d’architectures (interne et externe au logiciel) doivent permettre à ces deux systèmes de cohabiter.

Nous allons donc prendre un peu de recul sur la conception logiciel elle même et nous placer au dessus des patterns de conception que nous avons vu jusque ici pour regarder le logiciel de façon plus global. L’objectif est toujours de considérer le logiciel avec ses interfaces mais aussi de regarder au delà de celles ci pour penser l’architecture dans son ensemble.

Reprenons pour commencer l’exemple très simple de notre article sur le DAO [1]. Nous avions vu l’une des architectures les plus typiques, l’architecture en 3 couches permettant de dissocier les préoccupations métiers des préoccupations de représentations (la couche presentation) des préoccupations de stockage (la couches de données).

Il est évident que cette approche a de nombreux avantages, dans la conception d’une part car les spécificités des besoins des différentes couches seront isolés et confiés aux experts des différents domaines associés à ses couches. Dans la maintenance également car l'indépendance permet de substituer n’importe quelle couche par d’autres implémentations équivalentes ou même d’autres solutions techniques, tant que les interfaces entre couches sont respectées. Enfin ce genre d’approche faisant consensus parmis les développeurs, elle permet la mise en place de standard tant au niveau méthodologique qu’au niveau technique et aujourd’hui, parler par exemple de l’utilisation d’une architecture JEE en 3 couches fait tout de suite référence aux technologies JSP, JSF, Servlet pour la couche présentation, JPA, Hibernate pour les couches données tout en exploitant des EJB pour les couches métiers. Bien sur le modèle en 3 couches a ses limites et souvent il est necessaire de considerer quelques couches supplémentaires en ajoutant une couche de sécurité entre la présentation et le métier et une couche de cache entre le métier et la couche de données. Nous en obtenons alors le modèle en 5 couches [5].


Bien sur on peut en déduire des modèles à n couches mais cette approche a elle même des limites car en augmentant le nombre de couches, on augmente également le temps de traversé des flux de contrôle et de données, occasionnant une chute des performances.

Pour répondre à cette problématique, heureusement il existe également des solutions. Le problème de l’architecture en couche est d'être vertical. Ainsi, une solution est de fournir des fonctionnalités transverses déployées de manière horizontale.

De façon à concevoir des architectures plus horizontale, une approche dite orientée service est possible ou SOA [7]. Celle ci est apparue peu avant 2000 et a largement bénéficié de l'émergence des webservices. Le principe de SOA est simple, il se base sur le principe du producteur consommateur avec comme produit le service.

Pour orchestrer ces communications [8], un annuaire (UDDI) permet la définition d’un référentiel des services accessibles sur un bus de communication servant de middleware (souvent un ESB mais pouvant reposer plus simplement sur le Web si la sécurisation est complètement pris en compte). Enfin tous ces éléments vont reposer sur le standard xml pour faciliter l'interopérabilité de la communication et l'échange des données en restant conforme aux contrats définis pour les services en exploitant des support de communication comme SOAP ou JMS.

L'intérêt de ce genre d’architecture est clairement la mise en place d’un découplage entre les fonctions fourni par les services permettant à des équipes différentes de les développer indépendamment, sur des cibles potentiellement hétérogènes (OS, frameworks, language). de plus cela permet de faire vivre dans le même environnement des applications requièrant les même services ou partiellement. De même faire évoluer ce type de système se résume alors à l’identification des services à exploiter et des nouveaux à déployer.

La réside la difficulté de cette approche. Il faut être capable de dimensionner correctement les services de façon à ce qu’ils soient ni trop simple au risque d'être trop exploité avec des risques notable de perte de performances (à noter qu’un service est censé être unique donc c’est à lui de gérer l’exploitation de la demande et la monté en charge) soit trop complexe et donc trop peu réutilisable. Les architectures orienté service sont donc souvent confronté aux problèmes de performances qui sont directement relié aux capacités réseaux et aux capacités en temps de réponse des plus fragiles des services du système ( qui peuvent en ralentir ou d'encombrer drastiquement le fonctionnement) A noter que ce n’est pas le seul point noir car, si ls SOA favorise la modularité et le reuse des services, cela ne favorise pas son evoluabilité. Toutes transformations dans la chaîne des processus EBP (Enterprise Business Process) utilisant les services entraînera de forte perturbation sur l’ESB.

Pour palier à ces défauts les microservices proposent de descendre encore en granularité dans la décomposition fonctionnelle. Ainsi un microservice va se réduire à ne répondre qu'à une et une unique fonction tout en simplifiant les protocoles de communication par l’utilisation de REST par exemple et en supprimant le principe de bus. L’idée principale est de produire une fournir un plus grand nombre de services (en comparaison à SOA) mais plus simple, élémentaire et donc plus indépendant. Cela va alors faciliter leur maintenance et leur interchangeabilité.
Les microservices ont, au delà des aspects techniques, des avantages même au niveaux processus car grâce à leur indépendance, il est possible de morceler plus le système et donc de le rendre plus facilement testable. Cela conduit même au rapprochement du développeur dans cette phase de test qui aura la possibilité d’isoler complètement le service qu’il doit produire (on le voit arriver la le docker? ). Finalement, grâce à cela, l’utilisation des microservices va déplacer nos problématiques précédentes de performance et de maintenance en une problématique d’interconnexion et de logique de définition de processus métier. Attention alors à ne pas tomber dans des architectures plat de spaghetti…[6]

Jusque là nous nous sommes projetés dans des architectures sans parler de la structure réseau sous-jacente. Bien sur aujourd’hui une application ne se pense plus de façon stand alone sur un serveur, elles se pensent embarquées dans un ou plusieurs serveurs d’applications répartis sur plusieurs serveurs physiques et les approches de types SOA ou Microservices se prêtent parfaitement à cela mais si l’on ne considère pas forcément ce type de solutions, pourquoi malgré tout choisir une architecture distribuée et/ou décentralisé?

En réalité la réponse est simple, et c’est toujours la même, une architecture bien pensé doit permettre d'éviter de construire des monolithes immaintenables (remarque, si ca nous fait évoluer...) et la décomposition est souvent la première des bonnes idées à avoir.

Nous avons vu que ces principes font parti intégrante des architectures précédentes. En fait si les architectures distribuées et/ou décentralisées sont intéressantes c’est parce que non seulement elle poussent à décomposer le problème et favoriser ainsi la réutilisabilité, la maintenabilité (le problème étant localement plus simple du coup) mais elles permettent aussi d'intégrer des solutions techniques répondant à des problèmes plus spécifiques et techniques exigés par le client comme des exigences de disponibilités, de montées en charges, de vitesse de traitement, etc...

source: Architecture fédérée

Par contre, il ne faut pas croire que parce que l’on va considérer une architecture distribuée et ou décentralisé, les problèmes sont terminées car cela peut aussi être une fausse bonne idées dans certains cas. En effet, souvent ce genre d'architecture nécessite d'incorporer au mieux des outils spécifiques permettant la gestion du problème considéré mais selon les cas, il faudra penser à des mécanismes spécifiquement développées par l'équipe de développement. Dans ce genre de cas, on s’engage souvent sur un chemin complexe car ce genre d'architecture ne sont pas simple à gérer soit même. Par exemple, si nous considérons le cas d’une architecture orienté données qu’est l’architecture fédérée, elle a pour objet de permettre la mise en collaboration de différentes bases de données pour donner l’illusion qu’elle n’en forme qu’une tout en leur permettant de rester indépendantes. Si l’idée est pertinente, dans la pratique (dans le cas de fédérations faiblement couplé), elle nécessite de constituer des vue spécifiques dans chacunes des bases, des schémas supplémentaires (privé : base initiale , export : données présenté aux autres bases, import : données reconstitué des autres base) et des mécanismes de reconstruction. Ce genre d’approche est très complexe à mener et doit être refait pour à chaque intégration d’une nouvelle base dans la fédération. Il sera probablement plus simple de réaliser une migration des données vers des bases repensées plus globalement.

Alors bien sur le cas des architectures fédérées n’est pas à généraliser et elle peuvent aussi trouver leur applications dans certains cas bien étudié. Il ne faut pas oblitérer que ce genre d’architecture permettent aussi de gérer des problématiques de redondances ou de répartition de charge pour obtenir des services en haute disponibilité.

Pour conclure cet article déjà assez long, on statuera sur le fait que toutes les architectures décrites jusqu'ici sont plutôt classiques alors aujourd’hui, de nouvelles architectures font leur apparition pour intégrer de nouveaux types de besoins comme l'intégration de service dans le cloud, le traitement de données massives, le besoin de procéder à des prévisions ou des profils de ventes grâce à du Big Data ou du Machine learning. Nous ne rentrerons pas dans le détails de celles ci maintenant mais nous y reviendrons.

Références:

[1] https://un-est-tout-et-tout-est-un.blogspot.fr/2018/03/design-pattern-dao.html
[2] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/10/la-conception-logicielle.html
[3] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/12/la-modelisation.html
[4] https://un-est-tout-et-tout-est-un.blogspot.fr/p/java.html
[5] https://jobprod.com/quelques-principes-darchitecture-dapplis-web-javajee/
[6] https://dzone.com/articles/microservices-vs-soa-is-there-any-difference-at-al
[7] https://www.piloter.org/techno/support/SOA.htm
[8] https://openclassrooms.com/courses/implementez-une-architecture-orientee-services-soa-en-java/mprendrecequecestunesoa

samedi 24 mars 2018

Maven : assembly, targz et jdeb

Aujourd’hui nous allons nous intéresser à la production du livrable issue de la production logicielle. Le sujet ne va pas etre forcement tres long a traiter tant maven permet de chose si l’on utilise les bons plugins.

La production du livrable impose avant tout la définition de celui ci. Souvent, il va être dépendant de la plateforme et du langage. Dans notre cas, nous allons tâcher de produire nos composants dans le contexte d’utilisation du langage Java et nous nous projeterons dans une plateforme linux, la debian et la plateforme windows.

Bien sûr on aurait pu passer par l’utilisation de profile maven dans lequel nous aurions spécifier selon l’OS différents plugins afin de répondre à cette problématique de production. Par exemple en définissant les profiles suivants:

<profiles>
 <profile>
  <activation>
   <os>
    <family>windows</family>
   </os>
  </activation>
  <build>...</build>
  ...
 </profile>
 <profile>
  <activation>
   <os>
    <family>linux</family>
   </os>
  </activation>
  <build>...</build>
  ...
 </profile>
</profiles>


Nous n'utilisons pas cette approche car d’une part non nécessaire puisque Java est multiplateforme et d’autre part elle complexifie lourdement la production (l’utilisation de profile n’etant une bonne chose que si on en peut vraiment pas faire autrement)

Au lieu de cela, nous allons plutôt tricher un peu et prendre le parti pris que nous souhaitons livrer simplement un targz pour windows et un deb sous linux (comme cela, on pourra produire le même jar et livrer d’un côté un script bash pour lancer notre appli et de l’autre un bat.) Pour répondre à ce problème nous allons utiliser le plugin maven-assembly-plugin.

Ce plugin est un plugin indispensable pour la plupart des builds maven car au delà de l’utilisation ce que nous allons en faire, il permet de faire surtout toute sorte de compositions, de construire tout type de paquet de livraison mais aussi de pouvoir construire des jar au contenu spécifique (nous y reviendrons mais pas dans cet article) .

Nous avons donc deux types de packaging à réaliser, un pour le targz et un autre pour le deb. On va configurer le plugin pour qu’il fasse les deux en même temps en définissant deux fichiers d’assembly:

<plugin>
 <groupid>org.apache.maven.plugins</groupid>
 <artifactid>maven-assembly-plugin</artifactid>
 <version>2.5.3</version>
      <configuration>
  <finalname>${project.artifactId}-${project.version}</finalname>
  <ignoredirformatextensions>true</ignoredirformatextensions>
  <outputdirectory>${project.build.directory}</outputdirectory>
       </configuration>

 <executions>
  <!-- Packaging debian -->
  <execution>
   <id>assembly-debian</id>
   <phase>prepare-package</phase>
   <goals>
    <goal>single</goal>
   </goals>
   <configuration>
    <appendassemblyid>false</appendassemblyid>
    <descriptors>
     <descriptor>src/main/assembly/assembly-debian.xml</descriptor>
    </descriptors>
   </configuration>
  </execution>
  <execution>
   <id>assembly-targz</id>
   <phase>package</phase>
   <goals>
    <goal>single</goal>
   </goals>
   <configuration>
    <appendassemblyid>false</appendassemblyid>
    <descriptors>
     <descriptor>src/main/assembly/assembly-targz.xml</descriptor>
    </descriptors>
   </configuration>
  </execution>
 </executions>
</plugin>


Quelques explications sur l’utilisation du plugin dans le pom. On précise le répertoire de sortie et le nom général de la composition. On spécifie ensuite par type de composition un élément de configuration en utilisant appendAssemblyId. Ceci permet d’ajouter le classifier “assembly” au nom de la composition, mais ici on va s’en passer. On met false. On précise également pendant quelle phase et quel goal les assembly devront être construit. Enfin on donne au plugin le fichier d’assembly qui précise le contenu de la composition.

Le fichier assembly est la dernière partie permettant de construire la composition. Il s’agit d’un fichier xml définissant plusieur choses: le format de sortie, les dépendances à joindre et ou les déposer et enfin les répertoires annexes que la composition doit contenir. Regardons celui du targz:

<assembly>
 <id>bundle</id>
 <formats>
  <format>tar.gz</format>
 </formats>
 <includebasedirectory>false</includebasedirectory>

 <dependencysets>
<!--  Inclusion interfaces classifier et librairies-->
  <dependencyset>
   <unpack>false</unpack>
   <scope>runtime</scope>
   <useprojectartifact>false</useprojectartifact>
   <useprojectattachments>true</useprojectattachments>
   <outputdirectory>/opt/monappli</outputdirectory>
   <includes>
    <include>*:${project.artifactId}:*:assembly:*</include>
   </includes>
  </dependencyset>
 </dependencysets>

  <filesets>
      <fileset>
          <directory>src/main/debian/etc/init.d</directory>
          <outputdirectory>/etc/init.d</outputdirectory>
          <usedefaultexcludes>true</usedefaultexcludes>
          <filtered>true</filtered>
          <includes>
              <include>monappli</include>
          </includes>
      </fileset>
  </filesets>
</assembly>


Inutile de commenter le format, il parle de lui-même. Les dependencySet se construisent pour incorporer à la composition les éléments des dépendances qu’il faudra ajouter et sous quel forme. Par exemple on peut spécifier si elles seront dézipper dans la composition (cela est pratique par exemple lorsque l’on veut fusionner des artifacts ensemble pour faire un jar), le répertoire ou ces éléments seront déposé et enfin un ensemble d'artifact déclarer sous la forme d’une regex.

Les fileSet précisent de la même manière que pour les artifacts l’ensemble des fichiers et répertoires à construire dans notre composition. Ces éléments sont généralement issus des ressources du projets et il est possible de réaliser du remplacement clef/valeur dedans (via le resources filtering). A l’issu de l’application de l’assembly, on aura alors tous ces éléments dans un tar.gz… cool!

Bon bien du coup on fait pareil pour le paquet debian non? et bien pas tout à fait. sur le principe, on va procéder de la même manière en utilisant un fichier d’assembly rigoureusement identique sauf sur le format que nous définirons a “dir”. Ce format ne fait rien a part préparer les éléments de la composition. Pourquoi prendre ce format? Tout simplement parce que le plugin assembly n’est pas capable de construire quelque chose d’aussi complexe qu’un paquet debian. On va donc s’appuyer d’un autre plugin pour faire cela: jdeb. Avant cela je vous renvoie à [3] pour compléter vos ressources avec les fichiers de contrôles qui vont bien et les intégrer à l’assembly.

Ceci étant fait, penchons nous sur le plugin jdeb. Celui ci se configure assez classiquement en précisant sa phase de build et son goal. A cela, on va preciser le nom du paquet debian, le repertoire des fichiers de controles, et enfin un ensemble de dataset precisant tous les repertoire, fichiers et autres resources qui va falloir ajouter au paquet. Attention, a ce nieau, il faut avoir une vision clair des droits qui seront appliqué lors de l’installation, puisque l’on va les preciser ici. Voyons cela:

<plugin>
 <artifactid>jdeb</artifactid>
 <groupid>org.vafer</groupid>
 <version>1.5</version>
 <executions>
  <execution>
   <phase>package</phase>
   <goals>
    <goal>jdeb</goal>
   </goals>
   <configuration>
    <deb>${project.build.directory}/${project.artifactId}-${build}_${versiontimestamp}_all.deb</deb>
    <verbose>true</verbose>
    <controldir>${project.build.directory}/${project.artifactId}-${project.version}/DEBIAN</controldir>
    <dataset>
     <data>
      <src>${project.build.directory}/${project.artifactId}-${project.version}/opt</src>
      <type>directory</type>
      <mapper>
       <type>perm</type>
       <prefix>/opt</prefix>
      </mapper>
     </data>
    </dataset>
   </configuration>
  </execution>
 </executions>
</plugin>


Voilà, nous venons de passer en revu comment construire les livrables de nos projets. Bien sur faire du java permet déjà de packager les sources mais pour être propre, l'idéal est de fournir un composant standard comme un targz ou mieux un deb contenant tous les éléments permettant de faire vivre l’application (fichier de conf, de scripts, etc…)

Références: 

[1] https://maven.apache.org/plugins/maven-assembly-plugin/single-mojo.html
[2] https://github.com/tcurdt/jdeb/blob/master/src/examples/maven/pom.xml
[3] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/12/construire-un-paquet-debian.html

mercredi 21 mars 2018

JEE : Petite Intro

Nous avons traité dans quelques articles précédents de l'évolution de Java en partant de la version 1.4 jusqu'à presque aujourd’hui et la version 9 [1]. Il faut savoir qu’au fils de ces années, cette évolution a été suivi par sa version Entreprise. Celle ci nommé sous l’acronyme de J2EE (Java 2 Enterprise Edition) quand java n’en était encore qu'à la version 1.4, elle porte maintenant l’acronyme simplifié JEE (Java Enterprise Edition).

Ainsi avant d’entrer dans les détails techniques propres à cette technologie, je vous propose de faire un petit tour d’horizon des fonctionnalités offertes par cette version entreprise de Java [2].

Java pour l’entreprise est apparue en 1998 et a rapidement intégré les premières API importante du noyau JEE que l’on connaît encore bien aujourd’hui avec les :Servlets, JSP, EJB, JTA, JNDI, JavaMail et JMS (on notera aussi des extension JDBC car JPA n’existait pas encore et JAF).

Rapidement se sont greffés des API très connus comme la JSTL, JAXP (parseur xml) et moins connu comme JCA (Java Connector Architecture et non Java Cryptography Architecture de la version standard de Java) et JAAS (Java Authentication and Authorization Services qui en fait a ete ajoute au java standard mais qui va beaucoup plus etre utile sur les application web et donc JEE).

C’est avec la version 1.4 que J2EE a commencé à se segmenter autour de trois axes. Les techno Web (WebServices, XML, Servlets, JSP, JSTL et les nouvelles JSF), les technos d’entreprises ( avec les fameux et redoutés EJB 2) et enfin les technos de management et de sécurité (JMX et JACC mais oublions le…. ^^)

L’arrivée de Java 5 a été un bouleversement pour l'écosystème java. Java EE 5 a fait de même. La partie Web s’est enrichie de StAX pour le streaming Xml et de SAOP tandis que la partie Entreprise a enfin vu naître les EJB 3 et JPA (issu des techno Hibernate).

JEE 6 a introduit la notion de WebProfile permettant une simplification de la certification JEE pour les conteneurs applicatif leger. On a ainsi pu avoir des conteneurs d’applications se focalisant essentiellement sur le seul respect du web profile. Le WebProfile ne contient par définition que les parties cœurs de JEE, c’est à dire la partie Web (JSP,JSTL, Servlet, etc) en excluant la partie Web service et la Partie Entreprise, c’est à dire les EJB (en version simplifié “Lite”) JPA, et JTA. La version 6 de JEE a aussi amener de nouvelles API: un enrichissement de la partie Web Service avec JAX-RS et JAXM des ajouts dans la partie entreprise comme CDI (issu des technos Spring), les interceptons (équivalent aux filtres de la partie servlet) [3,4]

En 2013 est ensuite venu JEE 7 avec l’ambition de renouveler les technologies permettant la communication entre le front et le back. La partie Web a donc intégré un parseur JSON, l’API WebSocket (tout ça pour HTML5) et l'amélioration des servlets en proposant un mécanisme asynchrone et donc des outils pour gérer la concurrence au sein de la partie Entreprise. A cela, cette composante de JEE s’est également doté d’un mécanisme de Batch permettant l'exécution de traitement spécifique, parallélisable, etc… Donc pas de grosse révolution mais une volonté clairement affichée de suivre le mouvement, a un moment ou le monde du dev à basculer dans le BigData, le cloud, les archi distribuées, (l’IA), etc… [5]

Enfin Java EE 8 ! Sortie en septembre dernier [6], elle repose sur le socle de Java 9. Dans la continuité de JEE 7 ou l’on sentait bien une volonté de tendre vers les technologies Cloud, BD et distribué, celle ci propose des évolutions technique de l’existant et quelque nouveauté comme la prise en charge de la nouvelle norme HTTP/2 ou l'ajout de l'API JSON-B. Certains diront que son évolution a manqué le coche cette année mais JEE 9 étant prévu cette année, JEE 8 doit plutôt être vu comme un temps de pause et une bonne prise d’appuis (avec le passage à java 9 et ses modules) avant un nouveau rebond (espérons le!)

Voila, un petit tour d’horizon historique sur JEE qui n’engage que moi qui attend de voir ce que nous donnera les futur évolutions. En attendant celle-ci, je vous proposerai donc de poursuivre cet article par une liste d’articles traitant du contenu technique de JEE par la pratique. Cet article en fait donc une petite introduction que, j'espère, vous aurez appréciée.

Référence 

[1] https://un-est-tout-et-tout-est-un.blogspot.fr/p/java.html
[2] https://en.wikipedia.org/wiki/Java_EE_version_history
[3] http://alexander.holbreich.org/javaee5-vs-javaee6/
[4] https://blog.xebia.fr/2012/02/01/java-ee-6-une-plateforme-simple-et-legere-spring-na-qua-bien-se-tenir/
[5] http://blog.ippon.fr/2013/06/18/java-ee-7-plus-quun-nouveau-numero-de-version/
[6] http://www.oracle.com/technetwork/java/javaee/tech/index.html

samedi 17 mars 2018

IA : Lecture

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...)

Enfin voila, lisez le!

Références:

[1] http://un-est-tout-et-tout-est-un.blogspot.fr/2018/02/ia-petit-point-de-litterature.html

jeudi 15 mars 2018

Maven : Faire un plugin

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:

<groupid>org.tc.maven.plugin</groupid>
<artifactid>tc-project-info-extractor-maven-plugin</artifactid>
<name>${project.artifactId}-${project.version}</name>
<version>${build}${snapshot}</version>
<packaging>maven-plugin</packaging>


On va utilise le plugin maven maven-plugin-plugin [2] :

<plugin>
<groupid>org.apache.maven.plugins</groupid>
<artifactid>maven-plugin-plugin</artifactid>
<version>3.5.1</version>
</plugin>


On va ajouter des dépendances maven qui nous permettront de construire notre Mojo:

<dependencies>
 <dependency>
  <groupid>org.apache.maven.plugin-tools</groupid>
  <artifactid>maven-plugin-annotations</artifactid>
  <version>3.3</version>
  <scope>provided</scope>
 </dependency>
 <dependency>
  <groupid>org.apache.maven</groupid>
  <artifactid>maven-plugin-api</artifactid>
  <version>3.2.3</version>
 </dependency>
 <dependency>
  <groupid>org.apache.maven</groupid>
  <artifactid>maven-project</artifactid>
  <version>2.2.1</version>
 </dependency>
</dependencies>


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 :

$ mvn org.tc.maven.plugin:tc-project-info-extractor-maven-plugin:extract


soit son nom court:

$ mvn tc-project-info-extractor:extract


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.

@Mojo(name = "extract", defaultPhase = LifecyclePhase.CLEAN)
public class ProjectExtractorMojo extends AbstractMojo {

    @Component
    MavenProject project;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        getLog().info("ProjectExtractorMojo execution begin");
        getLog().info(project.getName());
        try {
            final BufferedWriter bos = new BufferedWriter(new FileWriter("target/resultFile.csv", true));
            bos.newLine();
            bos.write(project.getPackaging());
            ...
            bos.write(project.getName());
            bos.flush();
            bos.close();
        } catch (final Exception e) {
            e.printStackTrace();
        }
        ...
        getLog().info("ProjectExtractorMojo execution finish");
    }
}


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:

Références


[1] http://maven-guide-fr.erwan-alliaume.com/maven-guide-fr/site/reference/writing-plugins-sect-plugin-prefix.html
[2] https://maven.apache.org/plugin-tools/maven-plugin-plugin/examples/using-annotations.html#POM_configuration
[3] https://dzone.com/articles/a-simple-maven-3-plugin
[4] https://www.codeproject.com/Articles/1013928/Maven-Plugin-Development
[5] http://maven.apache.org/guides/plugin/guide-java-plugin-development.html
[6] http://maven.apache.org/plugin-developers/
[7] https://maven.apache.org/plugin-tools/maven-plugin-plugin/descriptor-mojo.html#skipErrorNoDescriptorsFound

lundi 12 mars 2018

Les annotations


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é:

   <modules>
    <module>tc-annotation</module>
    <module>tc-processor</module>
    <module>tc-helloworld</module>
   </modules>


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:

package annot;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Relation {}



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.

 <build>
  <plugins>
   <plugin>
    <groupid>org.apache.maven.plugins</groupid>
    <artifactid>maven-compiler-plugin</artifactid>
    <version>3.7.0</version>
    <configuration>
     <verbose>true</verbose>
     <source></source>1.8
     <target>1.8</target>
     <compilerargument>-proc:none</compilerargument>
    </configuration>
   </plugin>
  </plugins>
 </build>

 <dependencies>
  <dependency>
   <groupid>org.tc.test</groupid>
   <artifactid>tc-annotation</artifactid>
   <version>0.1.0-SNAPSHOT</version>
  </dependency>
  <dependency>
   <groupid>postgresql</groupid>
   <artifactid>postgresql</artifactid>
   <version>9.1-901-1.jdbc4</version>
  </dependency>
 </dependencies>


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 :

   <plugin>
    <groupid>org.apache.maven.plugins</groupid>
    <artifactid>maven-compiler-plugin</artifactid>
    <version>3.7.0</version>
    <configuration>
     <verbose>true</verbose>
     <source></source>1.8
     <target>1.8</target>
     <testsource>1.8</testsource>
     <testtarget>1.8</testtarget>
     <annotationprocessorpaths>
      <path>
       <groupid>org.tc.test</groupid>
       <artifactid>tc-processor</artifactid>
       <version>0.1.0-SNAPSHOT</version>
      </path>
     </annotationprocessorpaths>
     <!-- Soit les deux lignes suivantes soit le fichier dans le repertoire 
      services du jar contenant le processor -->
     <annotationprocessors>
      <annotationprocessor>proc.HWProcessor</annotationprocessor>
     </annotationprocessors>
     <!-- Ne pas faire de fork quand on utilise un processeur -->
     <!-- <fork>true</fork> -->
    </configuration>
   </plugin>



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:

<dependencies>
  <dependency>
   <groupid>org.tc.test</groupid>
   <artifactid>tc-annotation</artifactid>
   <version>0.1.0-SNAPSHOT</version>
  </dependency>
  <dependency>
   <groupid>org.tc.test</groupid>
   <artifactid>tc-processor</artifactid>
   <version>0.1.0-SNAPSHOT</version>
   <scope>compile</scope>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <plugin>
    <groupid>org.apache.maven.plugins</groupid>
    <artifactid>maven-compiler-plugin</artifactid>
    <version>3.7.0</version>
    <configuration>
     <verbose>true</verbose>
     <source></source>1.8
     <target>1.8</target>
    </configuration>
   </plugin>
  </plugins>
 </build>



Voila il ne reste plus qu’a builder notre module:

mvn clean install


ok ca marche:

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] tc-annotation-0.1.0-SNAPSHOT ....................... SUCCESS [  4.078 s]
[INFO] tc-processor-0.1.0-SNAPSHOT ........................ SUCCESS [  1.929 s]
[INFO] tc-helloworld-0.1.0-SNAPSHOT ....................... SUCCESS [  1.223 s]
[INFO] tc-annotation-module-0.1.0-SNAPSHOT ................ SUCCESS [  0.038 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------



Voyons voir les log de notre processeur:

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.

Références :

[1] Evolution Java 5

mercredi 7 mars 2018

Design Pattern : DAO


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]):


public class Task {
 private String title;
 private String description;
 public enum State {READY, STARTED, DONE} ;
 public State state=State.READY;
 private List<User> users=new ArrayList<>();
 public Task(String title,String description){
  this.title=title;
  this.description=description;
 }
}
public class User {
 private String name;
 private List<Task> tasks=new ArrayList<>();
 public User(String name){
  this.name=name;
 }
 public void work(Task t){
  t.state=State.STARTED;
 }
 public void done(Task t){
  t.state=State.DONE;
 }
}


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:


public interface IUserDAO {
 boolean create(User user);
 User read(String name);
 boolean update(User user);
 boolean delete(String name);
}
public interface ITaskDAO {
 boolean create(Task task);
 Task read(int identifiant);
 boolean update(Task task);
 boolean delete(int identifiant);
}


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:


public class FactoryDAO {
 private String url;
 private String userDB;
 private String pwdDB;
 private FactoryDAO( String url, String userDB, String pwdDB ) {
  this.url = url;
  this.userDB = userDB;
  this.pwdDB = pwdDB;
 }
 public static FactoryDAO getInstance() throws ClassNotFoundException {
  String url="UrlBD";
  String driver="Driver";
  String userDB="UserBD";
  String pwdDB="mdpBD";
  Class.forName( driver );
  return new FactoryDAO(url, userDB, pwdDB);
 }
 public Connection getConnection() throws SQLException {
  return DriverManager.getConnection(url, userDB, pwdDB);
 }
 public IUserDAO getUserDao() {
  return new UserDAOImpl(this);
 }
 public ITaskDAO getTaskDao() {
  return new TaskDAOImpl(this);
 }
}


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:


public class UserDAOImpl implements IUserDAO {
 private FactoryDAO factory;
 public UserDAOImpl(FactoryDAO factory ) {
  this.factory=factory;
 }
 @Override
 public boolean create(User user) {return false;}
 @Override
 public User read(String name) {return null;}
 @Override
 public boolean update(User user) { return false;}
 @Override
 public boolean delete(String name) { return false;}
}
public class TaskDAOImpl implements ITaskDAO {
 private FactoryDAO factory;
 public TaskDAOImpl(FactoryDAO factoryDAO) {
  this.factory=factoryDAO;
 }
 @Override
 public boolean create(Task task) { return false; }
 @Override
 public Task read(int identifiant) { return null; }
 @Override
 public boolean update(Task task) { return false;}
 @Override
 public boolean delete(int identifiant) { return false; }
}


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.


private static String SELECT="select name from User";
@Override
public User read(String name) throws SQLException {
 PreparedStatement statement=this.factory.preparedStatement(SELECT);
 ResultSet result=statement.executeQuery();
 return this.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....)

Références:

[1] https://fr.wikipedia.org/wiki/Objet_d%27acc%C3%A8s_aux_donn%C3%A9es
[2] http://www.oracle.com/technetwork/middleware/toplink/overview/index.html
[3] http://hibernate.org/
[4] http://un-est-tout-et-tout-est-un.blogspot.com/2017/12/design-pattern-visiteur.html
[5] http://un-est-tout-et-tout-est-un.blogspot.com/2017/11/design-pattern-proxy.html
[6] http://un-est-tout-et-tout-est-un.blogspot.com/2017/11/design-pattern-observateur.html
[7] http://un-est-tout-et-tout-est-un.blogspot.com/2017/11/ready-design-pattern-mediateur.html
[8] http://un-est-tout-et-tout-est-un.blogspot.com/2017/11/design-pattern-le-pont.html
[9] http://un-est-tout-et-tout-est-un.blogspot.com/2017/11/design-pattern-factory.html
[10] https://www.supinfo.com/articles/single/3637-operations-crud
[11] https://www.jmdoudoux.fr/java/dej/chap-javabean.htm
[12] https://java.developpez.com/faq/jdbc?page=Les-instructions-parametrees-moins-PreparedStatement#Qu-est-ce-qu-un-PreparedStatement
[13] http://blog.xebia.fr/2010/01/07/transactions-les-concepts-et-modeles/

samedi 3 mars 2018

IA: Notions de mathématiques élémentaires pour l'IA

Dans la perspective de faire une série d’articles sur l’IA, quelques notions mathématiques vont être abordés et donc nécessaires. J’avoue que j’ai eut du mal à écrire cet article car je ne sais pas si je dois me borner a juste présenter les quelques notions dont nous avons besoin ou si il faut partir dans des explications plus profondes afin de mieux en comprendre les mécanismes.

Alors ma première idée était de présenter de façon théorique les mathématiques sous jacente au machine learning et au deep learning cependant, bien que les quelques outils essentiels se résument globalement à la manipulation de vecteur, de l’utilisation du calcul matriciel, des combinaisons linéaires, d’algo d’apprentissage, des dérivées partielles (de fonction de coûts, d’erreurs quadratiques) et quelques notions en probabilités, cela fait énormément de base théorique à développer.

Or même si l’envie y est, je n’ai pas forcément le temps de développer tout cela. En fait il faudrait globalement replonger dans la moitié du programme de math de classe prépa, et si d’une part, la masse est conséquente, je ne pense pas que cela soit franchement pertinent pour aborder ensuite ce qui nous intéresse vraiment : l’IA.

Deja que globalement, en employant les frameworks adéquats du domaine, aujourd’hui, une bonne partie même des aspects mathématiques peuvent être masqués. Et si l’on a une bonne connaissance du comportement que l’on peut attendre de tel ou tel algo ou fonction de coût, il est presque inutile d’aborder ces sujets. Cependant, ceci est une vision très réductrice et je pense qu’une connaissance même succincte de la forme de ces algorithmes et que savoir quels sont les mécanismes de calcul sous jacent est un plus pour parfaire et optimiser nos choix sur le traitement des données et le choix des modèles à employer.

Partie Espace vectoriel

Bien nous allons aborder les bases supportant le domaine du machine learning. en l'occurrence ici les espaces vectoriels. Ne partez pas en courant, c’est juste pour dire que l’on a utiliser des vecteurs et des matrices!

Pour aborder le sujet, nous considérerons dans la suite de l'article deux espaces vectoriels
et

de base respectivement
et

Les vecteurs

Ainsi on appellera vecteur tout triplet
noté

définie dans tel que


ou de façon généralisée:


Par convention on pourra noter le vecteur comme suit (notation vectorielle) :


La définition du vecteur que nous avons utilisé précédemment par la combinaison linéaire des éléments de avec les vecteurs de base va nous permettre plein de chose.

La multiplication par un scalaire tel que:


L’addition de deux vecteurs:


On peut évidement évoquer le produit scalaire ou le produit vectoriel (souvent vu en géométrie euclidienne et utile en mécanique) mais ceux ci ne nous apporteront pas immédiatement grand chose a part dans les Machines a vecteurs de support (SVM)[1] mais nous y reviendrons.

Pour introduire le produit scalaire, il nous faut d'abord la notion de projection. Ainsi la projection d'un vecteur sur un autre, permet de construire un nouveau vecteur dirigé par normalisé [2] tel que:


Ainsi si l'on considère le produit scalaire , défini dans la même base alors on a un valeur scalaire telle que:


ou plus habituellement


A noter que dans le cas ou les deux vecteurs sont définis dans des base differentes, il sera nécessaire d'appliquer une transformation a l'un des deux vecteur afin que celui ci soit définit dans la même base.

Pour cela, il est nécessaire de définir les combinaisons linéaires de passage de la première base vers la seconde (faire la réciproque permettra d'obtenir le résultat dans la base initiale). Pour cela, nous verrons que l'utilisation des matrices, bien que pas forcement nécessaire, sera d'une grande aide.

Maintenant si l'on considère le produit vectoriel , défini dans la même base alors on obtient un nouveau vecteur tel que:


avec les propriétés suivantes:

est orthogonal a et . (leur produit scalaire est nul) formant une base direct.

Les matrices

Considérons toujours les deux espaces vectoriels précédemment donnés munis de leur base respective. Considérons l'application:


Ainsi tous les vecteurs de admettent une décomposition selon un ensemble de vecteurs de tel que les vecteurs de base on a:


On notera alors M l'ensemble des paramétres m organisés selon une grille a i ligne et j colonne:


Ainsi on a pour tous les vecteurs et de et , les matrices colonne V et U telle que:


Nous ramenant au système d’équations:



Ceci définissant les bases sur la nature des matrices, je vous invite a vous projeter dans quelques documents plutôt simple a comprendre afin d'en percevoir le très grand potentiel de cet outil [3].

On peut donc maintenant passer aux opérations de base sur les matrices que sont les additions et la multiplication (attention celle ci n'est pas commutative). Considérons alors pour la suite les matrices 3X3, A et B .

L'addition matricielle:

Multiplication matricielle

Partie statistique

Maintenant que nous avons traités des structures nécessaires au machine learning, intéressons nous aux algorithmes. Globalement, nous avons vu ce que nous allions manipuler pour l'IA mais il reste encore les traitements particuliers qui peuvent être utiliser.

Par exemple, il nous faudra être capable d’évaluer l'erreur que fait notre modèle de machine learning en utilisant par exemple l'erreur quadratique moyenne ou (MSE, mean square error). Celle-ci étant utilisé très souvent sous differentes formes et déclinaison afin d'une part mesurer la performance des modèles et procéder a leur apprentissage, il est intéressant d'en présenter quelques notions.

L'erreur quadratique moyenne permet la mesure globale de l’écart entre la valeur désirée et celle obtenue calculé sous la forme d'une moyenne. Celle ci est construite de façon quadratique pour que les erreurs ne se compensent pas autour de la moyenne.

L'erreur moyenne se définit tel que:


L'erreur quadratique moyenne se définit tel que:


A noter que généralement on utilise l'erreur quadratique sans la racine puisque utiliser de façon continue sur l'ensemble des données, la mesure de performance reste homogène. La notation chapeau représente la valeur estimé par le modèle (donc la fonction dépendante des paramètres du modèle qui sont à ajuster), en opposition avec celle réelle.

Sur la base de l'erreur quadratique pourront etre ajouté divers paramètres complémentaires permettant un ajustement dynamique des paramètres du modèle. L'apprentissage sera alors plus ou moins efficace et ou rapide, sachant que généralement, ces deux propriétés seront assez antagoniste.

Enfin de façon a permettre un apprentissage efficace, on procédera au calcul du gradient de l'erreur (c'est a dire l'ensemble des dérivés de la fonction selon l'ensemble de ses paramètres). Celle ci aura l’intérêt de garantir la convergence de la procédure d'apprentissage en garantissant l'orientation de la correction a appliquer au fils des itérations d'apprentissage.

Sans entrer dans le détail, si l'on considére la fonction suivant:


alors le gradient se notera:



et son hessien (le gradient du gradient) formant la matrice hessienne:


Voila, je ne pense pas que cette liste est exhaustive des outils mathématiques nécessaires a l'IA mais pour tous ceux manquant (comme les fonctions d'activations possible), nous prendrons le temps de les explicités le moment venu au cas par cas. En tout cas nous voyons rapidement que le domaine de l'IA nécessite de bonnes et larges bases théoriques comme technique (nous le verrons dans les prochains articles).

Références:

[1] https://zestedesavoir.com/tutoriels/1760/un-peu-de-machine-learning-avec-les-svm/
[2] https://fr.wikipedia.org/wiki/Norme_(math%C3%A9matiques)
[3] https://webusers.imj-prg.fr/~nicolas.laillet/Methodo1.pdf