Thématiques principales

Affichage des articles dont le libellé est Ioc. Afficher tous les articles
Affichage des articles dont le libellé est Ioc. Afficher tous les articles

vendredi 28 décembre 2018

OSGI : Concepts généraux


Aujourd’hui nous allons parler d’un framework qui me tient à coeur: OSGI. Cela faisait un moment que je voulais écrire un article sur celui ci mais beaucoup d’autre sujet, se sont trouvé être plus intéressant sur le moment.

À vrai dire, OSGI est clairement très intéressant mais l’utilisant depuis déjà près de 8 ans, malgré l'intérêt qu’il a intrinsèquement, il ne porte plus pour moi ce goût de nouveau que peuvent avoir les sujets comme l’IA... , c’est évident.

Pourtant voilà, je me suis dit, il est temps de faire le point sur le sujet et de le mettre un peu plus sous les projecteurs, car mine de rien, quasiment tout le monde l’a un jour utiliser, mais très peu de gens le savent!

Mais repartons du début!

Historique

OSGI [OSGI], [wikipedia] ou Open Service Gateway Initiative est un framework de gestion de composants et de services. Initialement prévu pour le monde de l’embarqué (si si ^^), il a été utilisé par des constructeur comme Volvo, BMW ou encore Cisco et s’emploie dans les produits logiciels à destination du monde de la domotique et des systèmes de sécurités

Nous le verrons plus en détail mais pourquoi OSGI? parce que ce framework est léger (en dehors de la JVM) et permet le chargement et le déchargement de ses librairies à chaud tout en facilitant la manipulation de ces dernières dans des versions différentes simultanément.

Issu de la JSR 8 [JSR8], OSGI a été créé dans les années 2000 et a évolué régulièrement. Ainsi aujourd’hui la spécification OSGI en est à la version 7 [OSGI-V7] avec une implémentation de référence fourni par l’Alliance OSGI qui ne doit pas être considéré comme une version de production mais “juste de références”. Ainsi différentes versions open sources ont été implémenté pour répondre à des besoins plus techniques et se sont accaparé ces différentes versions des spécifications comme equinox [equinox], knoplerfish [knoplerfish], concierge [concierge] ou encore felix [felix].

Aujourd’hui la spécification 7 de OSGI forme un pavé de plus de 1000 pages de documentation [OSGI-V7-CORE] et il nous est impossible d’en faire un résumé exhaustif ici cependant dans cet article nous tâcherons de passer en revu les concepts pivots de OSGI pour en comprendre les mécanismes clefs puis nous nous intéresserons aux différentes implémentations ainsi que leur intégrations dans les frameworks plus classique (mais qu’on sait pas que OSGI est dedans…. O_o)

OSGI concepts généraux

OSGI est donc un framework Java. Conçu il y a près de 18 ans, celui ci est un peu partout sans que personne ne le sache vraiment. On le trouve dans la plupart de nos serveurs d’applications JEE, jusque dans nos IDE comme eclipse qui à fondé sa architecture dessus… si si…

Bien sûr ce n’est pas parce que quelque chose est utilisé partout que forcément c’est bien…. c’est vrai on le voit tous les jours avec le framework Spring! non blague à part (ou pas), OSGI est partout mais pour de bonnes raisons et c’est justement sa discrétion qui en fait sa force et sa pertinence.

En effet, OSGI est un framework amenant des moyen technique de mise en oeuvre logicielle mais aussi des moyens conceptuelles, apportant avec lui quelques paradigme de modélisation simple mais efficace laissant libre le développeur de faire ce qu’il veut tout en lui garantissant plus de souplesse à son application.

Il fournit pour cela une implémentation à deux paradigmes importants en développement logiciel:

  • la modularité qui permet la gestion et l’articulation logique des éléments de l’application
  • les services qui fournissent les moyens de donner une opérationnalitée à ces éléments en leur donnant du sens.

La modularité dans OSGI

Sans aller trop loin dans le débat de la modularité [serv-side], on rappellera quelques principe de façon à poser ce à quoi nous attendre dans OSGI

La modularité est une approche de conception permettant la séparation des préoccupations, que celles-ci soient fonctionnelle, ou technique.

Bien sur il existe différents degrés de modularité et celle-ci s’appliquant sur la solution logicielle à des niveaux très variés.

Il est ainsi possible de concevoir de façon modulaire tant au niveau de l’architecture système que de l'architecture logicielle. Quel que soit l’objectif métier, le but est de permettre de scinder les problématiques et amener du découplage facilitant ainsi: la réalisation, le test, la compréhension, la maintenance, la mise à jour, etc...



Pour mettre en oeuvre de la modularité dans une architecture, il existe divers solutions et implémentations: certains design pattern comme l’observateur [observ], l’association du pattern proxy et factory [invochand] ou encore l’utilisation de contrat de services avec des interfaces et des API comme illustré ci dessus.

OSGI propose une autre alternative en se situant à la croisé des chemins de ces approches. Nous en avions parlé dans l’article [whriteboard]. En résumé, la modularité consiste en la construction de composants interdépendants via des interfaces (API). Des lors, l’utilisation d’un module par un autre passe par une api et masque donc l’identité du composant réellement utilisé, c’est le principe du masquage de l'implémentation permettant ainsi découplage, réutilisation, capacité à être testé etc…

Cependant, on constate une limite a l’utilisation des contrats de service et des API : il faut etre capable de re-associer les composants interdépendants [serv-side]. Il faut pour cela utiliser des patterns comme la Factory et au final déplacer le couplage vers la factory [whiteboard].


Alors bien sur il existe d’autres approches pour répondre à cette problématique avec les framework d’IoC comme Spring (implémentant le pattern Hollywood [hollywood]) utilisant des xml ou des annotations. Pourtant ces solutions sont assez peu satisfaisantes car elles confient les détails d'implémentations à un acteur tiers qui aura la charge de faire communiquer les composants entre eux sur la base d’une description donné statiquement. Finalement cela re-créera un couplage car si en effet dans Spring (par exemple) on spécifie la dépendance d’un composant via une API et des interfaces, il faudra lui spécifier un à moment l'implémentation qui y répondra et cela de façon statique par configuration.

Le pattern Broker

OSGI fournit une alternative en utilisant le pattern Broker [broker] permettant, au travers d’un registre de bundle (nom des composants ou modules OSGI) et de service, de déporter le couplage en dehors de la préoccupation des composants mis en interaction.

Il s’agit avant tout d’un pattern d’architecture (non d'implémentation) qui classiquement s’utilise dans le cadre de service de messagerie comme JMS mais qui ici se focalise sur la mise en collaboration des bundles entre eux, à la demande, et ce géré par le registre.

Dans sa mise en oeuvre, un ensemble de composants (les bundles) vont s’enregistrer auprès du registre comme fournisseur de services liés à différents contrats d’interfaces. Ensuite, par l'intermédiaire du registre, ils seront appelé par d’autres bundle ayant besoin des services remplissant les contrats d’interfaces souhaités.

Dans ce principe, aucun bundle ne se connait directement, tout passe par le registre qui aura à gérer l’ensemble des services offerts et les clients qu’il faudra rediriger vers les bonnes implémentations.


Les services

OSGI est une implémentation du pattern Broker. Il s’agit donc en réalité d’un framework permettant la conception logicielle de façon modulaire mais et surtout de construire des applications selon une logique orienté service.

Ainsi comme nous venons de le voir la modularité va nous amener à séparer les préoccupations, les services vont en plus nous amener à réfléchir nos bundles selon le rôle qu’ils ont à mener dans notre application de façon à en exposer juste et seulement leur valeur ajouté.

L’idée est la encore et toujours la modularité, mais en ayant une forte décomposition, on maximise alors le re-use car un service implémentant un contrat de service pourra être mis à disposition par le registre à tous les bundles en ayant le besoin.

Vous allez dire mais ça Spring le fait déjà au final le registre, c’est le fichier xml de Spring.

Effectivement mais OSGI va plus loin:

  • D’une part, l’utilisation du registre va autoriser un bundle à consommer un service à la demande et de s’en défaire, le couplage entre les bundles est dynamique et non permanent.
  • La conséquence du point précédent que nous détaillerons plus loin dans l’article est que cela permet la mise en ligne des bundles sans que ces derniers n’aient eu le besoin de résoudre à leur chargement toute leur dépendance et service.
  • Ceci nous amènera alors à la possibilité de gérer des versions concurrente d’un même service pour faciliter leur mise à jour à chaud.

Avec OSGI, la gestion des services par le registre des bundles nous laisse entrevoir quelques fonctionnalités dont nous n’avons pas vraiment l’habitude. Dans un projet classique, généralement nous pensons notre application comme un tout unique dont les éléments, librairies et services, devront évoluer conjointement. Mais avec OSGI, cette vision limitative devient caduc et il faudra penser l’application comme un système en perpétuel mutation dont les éléments nouveau côtoient des éléments anciens et dont les services seront utilisés selon le besoin.

Gestion des dépendances

Pour permettre des mécanismes de gestion à chaud des bundles et de la consommations dynamiques des services, OSGI va donc fournir un certain nombre de concepts focalisé sur une gestion un peu particulière des dépendances.

Cette gestion des dépendances va s’exprimer selon deux types [10min]:

  • les dépendances statiques
  • les dépendances dynamiques

Statiquement

La gestion statique des dépendances dans OSGI est assez similaire à la gestion des dépendances Java réalisé avec le classpath à ceci prêt que celui ci est plus stricte car embarque avec les lui le detail des versions impliqué dans la dépendance.

Cela paraît dans un premier temps très contraignant cependant cela permet en fait de faire vivre au sein du framework deux versions distinctes d’une même dépendance, laissant alors au composant qui en ont besoin d’utiliser celle avec laquelle il sera le plus en phase.

Ce type de dépendance se déclarent au sein du manifest java décoré et adapté au formalisme OSGI (nous verrons cela plus en détails dans le chapitre sur les bundles).

Ainsi les bundles lorsqu’ils seront chargé par le framework OSGI (on verra plus tard mais il s’agit de la phase d’installation) devront trouver dans leur classpath ou dans les classloader du framework les dépendances apportées par les autres bundles dans les versions attendues


Dynamiquement

Une fois installé, un bundle apporte un ensemble de dépendance statique, cependant, pour créer un minimum de couplage, il est préférable de s’appuyer autant que faire se peut sur le pattern broker.

Ainsi pour cela le framework OSGI nous amene à construire des bundles définissant des contrats des services ne contenant que des informations spécifiques aux contrats de services.

Ainsi ces contrats seront ensuite implémentés par un autre bundle qui aura la charge de l'implémentation du contrat de service. Ainsi, ce bundle s'enregistrer auprès du registre et lorsqu’un autre bundle demandera au registre OSGI un service conforme au contrat de service défini dans le bundle contrat alors le registre fournit l'implémentation.

L'intérêt de cette approche est qu’il est possible alors de proposer divers implémentation d’un même contrat de service.


Conclusion

Nous avons dans les grandes lignes les principes généraux de OSGI, son paradigme de modélisation modulaire et orienté service.

L'intérêt de son utilisation est double, d’une part de fournir une solution réelle de couplage des modules mais aussi la capacité de faire évoluer une application dynamiquement tout au long de son cycle de vie et ce, “à chaud”.

Il peut être intéressant maintenant d’en parcourir l’utilisation dans le concret (dans le prochain article sur OSGI) afin de mieux appréhender ses contraintes techniques.

Références

mardi 8 mai 2018

Iod, l'injection de dépendances

Nous avons vu dans l’article précédent, le principe de l’inversion de contrôle. Ce dernier se base sur les principe de SOLID et est indispensable aux frameworks modernes tels que Spring qui l’utilise, on peut presque le dire, à outrance dans leur système d’injection de dépendances.

Dans cet article, justement, je vous propose de regarder comment fonctionne un mécanisme d’injection de dépendance et vous invite donc si vous ne l’avez pas vu à regarder l’article [1] afin d’en acquérir les pré-requis.

J'aurais peut être dû faire un article commun pour traité les sujets de IOC et de IOD car les deux sont souvent intimement lié et même traité ensemble. Cependant, d’une part, le temps dont je dispose ces derniers temps c’est gravement amoindri donc c'était plus simple d’en faire deux et d’autre part, je trouve que si l’IOD dépendant de l’IOC, l’inverse n’est pas vrai et heureusement (ca serait presque paradoxale…. ^^)

Donc du coup voilà, qu’est que IOD où injection de dépendance [2]. Le principe est simple, il s’agit de donner la responsabilité de la construction de la dépendance entre deux objets a un troisième. A ce stade on peut se dire que ce que nous avions vu sur l’inversion de contrôle est suffisant, on crée une factory pour produire des objets et op on l'appelle lorsqu'il faut construire des objets qui en ont besoin. Si l’idée de base est bien celle-ci, il nous manque un partie importante, l’orchestration.

En effet, dans le principe, l’injection de dépendances repose globalement sur les même mécanismes que l’inversion de contrôle car, l’inversion de contrôle permet de déplacer la problématique de la gestion du cycle de vie d’un objet dont un objet en avait la responsabilité, auprès d’un autre. Du coup forcément, pour permettre la mise en relation, l’objet initial doit toujours pouvoir obtenir l’objet dont il a besoin.

Ainsi dans l’article précédent [1] on avait forcément en plus de l’IOC une forme implicite d’injection de dépendance.

def mFactory=new MoteurFactory()

new Voiture(mFactory.buildMoteurX()).drive()
new Voiture(mFactory.buildMoteurX()).drive()


Mais bon on s’accordera que ça mériterait, un petit peu plus d’outillage.

Ainsi, ce qui manque c’est d’un moyen permettant de faire cette mise en relation de la voiture avec le moteur implicitement. Le premier réflexe, qui n’est pas forcément une mauvaise idée, est de faire appelle à une autre factory. La combinaison des factory est souvent dans un périmètre limité une bonne solution.


class MoteurFactory
{
   def buildMoteurX()
   {
       return new MoteurX()
   }

   def buildMoteurY()
   {
       return new MoteurY()
   }
}

class VoitureFactory
{
   def buildWithMoteurX(MoteurFactory f)
   {
       return new Voiture(f.buildMoteurX())
   }

   def buildWithMoteurY(MoteurFactory f)
   {
       return new Voiture(f.buildMoteurY())
   }
}

def mFactory=new MoteurFactory()
def vFactory=new VoitureFactory()

def v1=vFactory.buildWithMoteurX(mFactory)
def v2=vFactory.buildWithMoteurY(mFactory)

v1.drive()
v2.drive()

Bien ça marche, cool mais en fait on se rend vite compte qu’on a déplacer le problème! ba oui car la on passe en paramètre la factory des moteurs à celle des voitures. On a donc aussi un problème de gestion de dépendance! Mais ne peut on pas faire une factory pour gérer cela? ba si allons jusqu'au bout de l’idée


class MoteurFactory
{
   def buildMoteurX()
   {
       return new MoteurX()
   }

   def buildMoteurY()
   {
       return new MoteurY()
   }
}

class VoitureFactory
{
   MoteurFactory facto

   def VoitureFactory(MoteurFactory facto)
   {
       this.facto=facto
   }
ca 
   def buildWithMoteurX()
   {
       return new Voiture(this.facto.buildMoteurX())
   }

   def buildWithMoteurY()
   {
       return new Voiture(this.facto.buildMoteurY())
   }
}

class MetaFactory
{
   def buildFactoryForCar()
   {
       return new VoitureFactory(new MoteurFactory())
   }
}

def mf=new MetaFactory()

mf.buildFactoryForCar().buildWithMoteurX().drive()
mf.buildFactoryForCar().buildWithMoteurY().drive()

Bien on voit que ça marche, mais ça fait beaucoup de factory! Et bien oui et c’est pour cela que l’on utilise des framework comme Spring [3] ou CDI [4].

Voilà, j'espère qu’avec ces deux articles et cet exemple très simple, on a un peu démystifier l’IOD et IOC et permit de mieux aborder les mécanismes sous jacent a des framework comme SPring et CDI qui se basent eux aussi (aux registres et annotations près) sur ce bon vieux pattern factory. A très vite donc sur Spring et CDI!

Références

[1] https://un-est-tout-et-tout-est-un.blogspot.fr/2018/05/ioc-inversion-de-controle.html
[2] http://igm.univ-mlv.fr/~dr/XPOSE2010/guicespring/di_presentation.html
[3] https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html
[4] https://rmannibucau.developpez.com/tutoriels/cdi/introduction-cdi/

samedi 5 mai 2018

Ioc, Inversion de controle

Dans les articles précédents, nous avons à traiter des sujets se rapportant à l'écosystème Java et entre autre a JEE avec JMS. Bien sûr nous ne nous arrêterons pas là et nous nous intéresserons à JPA, le JSP, les Servlet et les EJB. Cependant si ces concepts sont le cœur de JEE, il serait dommage de ne pas traiter également des alternatives possibles à JEE, comme par exemple Spring.

Dans cet article, nous n’allons pas traiter directement Spring mais introduire quelques concepts clefs afin d'appréhender au mieux les mécanismes qui en sont les fondamentaux : l’inversion de contrôle et l’injection de dépendance.

Commençons par l’inversion de contrôle. Nous l’avions évoqué dans un article précédent traitant des principes SOLID [1], nous avions traité de la problématiques de l’inversion de dépendances (principe D) qui par la construction d’entité abstraite, nous permet de découpler le code afin qu’il respecte au mieux le principe de responsabilité unique (principe S). Nous avions évoqué justement qu’en poussant le raisonnement plus loin, ce principe nous permet d’aller jusqu'à l’inversion de contrôle.

Bien sur il existe des patterns très bien fait qui nous permettent facilement de faire de l’inversion de contrôle comme par exemple le pattern Visiteur [2] ou le pattern Stratégie [3] couplé à un pattern factory [4]. C’est par cette deuxième approche que nous allons présenter l’inversion de contrôle (et donc implicitement de dépendances)

Posons le problème. Imaginons une voiture capable de rouler et pour cela, bien sur la voiture va employer un moteur. La première approche qui aura le mérite de marcher sera probablement d'écrire un code proche de celui ci (en groovy):


class Voiture{

   Moteur moteur=new Moteur()

   def drive() {
       this.moteur.active()
       println("la voiture avance")
   }
}

class Moteur
{
   def active() {
       println("le moteur tourne");
   }
}

def voiture=new Voiture()
voiture.drive()

Même s’il respecte une certaine décomposition, ce code souffre d’un problème de couplage fort entre les classes Voiture et Moteur. En effet, dans ce cas, il est impossible de prévoir une voiture avec un moteur qui serait différent en sortie d’usine! Il faudrait ajouter un moyen permettant de changer ce moteur après la construction comme une méthode supplémentaire du type changeMoteur. Ce n’est pas acceptable.

Ainsi pour répondre à cela, on va suivre le principe I de SOLID en rendant abstrait le moteur dans la voiture modélisant ainsi qu’il sera possible d’utiliser des moteurs différents.

Cela donne donc ceci:


class Voiture{

   Moteur moteur;

   def Voiture(Moteur m)
   {
       this.moteur=m;
   }

   def drive() {
       this.moteur.active()
       println("la voiture avance")
   }
}

abstract class Moteur{
   def abstract active()
}

class MoteurX extends Moteur{
   def active() {
       println("le moteur X tourne");
   }
}
class MoteurY extends Moteur{
   def active() {
       println("le moteur Y tourne");
   }
}

new Voiture(new MoteurX()).drive()
new Voiture(new MoteurY()).drive()

On a réussi à découpler le moteur de la voiture lors de sa création mais on a pas vraiment rationalisé la construction des moteurs pour les voitures. C’est donc la que va intervenir le pattern Factory en fournissant une entité unique permettant la création des moteurs (et cela nous permet de respecter le principe S). Voici donc comment évolue notre code, suite a l’ajout d’une factory:

class MoteurFactory
{
   def buildMoteurX()
   {
       return new MoteurX()
   }

   def buildMoteurY()
   {
       return new MoteurY()
   }
}

def mFactory=new MoteurFactory()

new Voiture(mFactory.buildMoteurX()).drive()
new Voiture(mFactory.buildMoteurX()).drive()

Voilà, les voitures n’ont plus la responsabilité de construire leur moteur, c’est la factory qui s’en chargera nous permettant de choisir le moteur le plus adapté. Du point de vue de la voiture, pour elle, cela reste transparent, puisque tous les moteurs que l’on peut lui donner respecte la charte d’utilisation (on respecte alors le principe I).

L’inversion de contrôle et l’inversion de dépendances ne sont pas des principes très complexe à appréhender ni à mettre en oeuvre pour le peu qu’on les démystifient . Il s’agit surtout d’un principe de décomposition [5], dont le but est le respect des principes SOLID.

Pour l’injection de dépendances, je vous invite au prochain rendez vous, dans un article suivant.

Références

[1] http://un-est-tout-ethttps://blog.imirhil.fr/2013/05/19/inversion-de-controle-cest-bon-mangez-en.html-tout-est-un.blogspot.fr/2018/04/solid.html
[2] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/12/design-pattern-visiteur.html
[3] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/11/design-pattern-strategie.html
[4] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/11/design-pattern-factory.html
[5] https://blog.imirhil.fr/2013/05/19/inversion-de-controle-cest-bon-mangez-en.html