Accueil > DynaResume, OSGi > Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step5]

Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step5]


Dans le billet précédant [step4] nous avons utilisé le registre de services OSGi pour consommer/fournir le service UserService. Nous avons montré que l’utilisation du registre de services OSGI, permettait de rendre opérationnel le lancement/arrêt du bundle org.dynaresume.services qui fournit le service UserService :

  • lorsque le bundle service org.dynaresume.services est arrêté, le bundle client org.dynaresume.simpleosgiclient qui souhaite consommer le service UserService, récupère une instance null.
  • lorsque le bundle service org.dynaresume.services est lancé, le bundle client org.dynaresume.simpleosgiclient qui souhaite consommer le service UserService, récupère l’instance UserService fournit par le bundle services.

Dans ce billet je vais expliquer 2 « bonnes pratiques » à suivre dans les Bundle OSGi :

Voici un schéma de ce que nous allons effectuer dans ce billet :

Ce schéma montre que :

I .Services & version bundle

I-A .Livraison & Bugs

Ici nous allons décrire un scénario classique de livraison d’une application. L’application livrée est installée et tous les utilisateurs se connectent dessus. Malheureusement le distribuable contient un service qui est buggé.

I-A-1 .JEE – WAR, EAR

Dans le cas de JEE, un distribuable est sous forme de WAR et EAR, et ceci pose les problèmes suivants :

  • la correction du bug engendre la livraison d’un nouveau distribuable WAR ou EAR. Autrement dit l’ensemble de l’application doit être relivrée. Il n’est pas possible de livrer un petit distribuable qui contiendrait uniquement la partie service corrigée. Packager un EAR peut devenir extrèmement long et coûteux ce qui rend la correction d’un EAR buggé extrêmement lente (éventuellement plusieurs jours).
  • l’installation du nouveau distribuable est tres lourde et engendre l’arrêt du serveur. Tous les utilisateurs seront pénalisés uniquement pour corriger l’utilisation d’un service (et il se peut que la plupart des utilisateurs n’y aient même pas accès).

I-A-2 .OSGi – JAR

Dans le cas de OSGi, un distribuable est sous forme de un ou plusieurs JAR par Bundle OSGi :

  • La livraison d’un JAR (Bundle) peut suffire pour corriger le problème.
  • l’installation du nouveau distribuable (JAR) est très légère (elle s’effectue via la console OSGi par la commande install) et peut s’effectuer à chaud. Ce « patch » est transparent pour les utilisateurs connectés.

Un Bundle OSGi est identifié par son ID (Bundle-SymbolicName) et sa version (Bundle-Version). Le « patch » à livrer consiste à créer un bundle qui corrige le bug (du service) en incrémentant sa version. Il est ensuite possible de lancer un même Bundle (même ID Bundle-SymbolicName) avec des versions différentes. C’est que je souhaite montrer dans cette section.

I-B .Bug UserService

Nous allons simuler une erreur dans le service UserServiceImpl du Bundle org.dynaresume.services de version 1.0.0.qualified et créer un nouveau bundle « patch » org.dynaresume.services de version 1.0.1.qualified qui corrige le problème. Nous verrons qu’avec notre architecture, note bundle « patch » ne fonctionnera pas, car nous devons scinder le Bundle org.dynaresume.services en 2 bundles services API et Implémentation.

Vous pouvez télécharger l’ensemble des projets expliqués dans cette section sur org.dynaresume_step5-patch.zip. Nous allons partir des projets du billet précédant org.dynaresume_step4-servicetracker.zip.

I-B-1 .Bundle org.dynaresume.services (1.0.1)

Ici nous allons créer le bundle « patch » org.dynaresume.services de version 1.0.1. Pour cela :

  • Copiez le projet org.dynaresume.services et nommez le org.dynaresume.services_patch.
  • Modifier la version du bundle en 1.0.1.qualifier dans le MANIFEST.MF :
    Bundle-Version: 1.0.1.qualifier

I-B-2 .Bundle org.dynaresume.services (1.0.0)

Ici nous allons modifier le bundle org.dynaresume.services de version 1.0.0 pour simuler un bug dans la classe org.dynaresume.services.impl.UserServiceImpl. Pour cela, modifiez cette dernière comme suit :

public Collection<User> findAllUsers() {
  throw new RuntimeException("Error into findAllUsers method.");
}

Relancez (via OSGi DynaResume) Equinox et la console OSGi affiche :

osgi> Start Bundle [org.dynaresume.domain]
Start Bundle [org.dynaresume.services]
Start Bundle [org.dynaresume.services]
Start Bundle [org.dynaresume.simpleosgiclient]
--- Get UserService from OSGi services registry with ServiceTracker ---
User [login=angelo, password=]
User [login=djo, password=]
User [login=keulkeul, password=]
User [login=pascal, password=]

Cette trace montre que les 2 bundles org.dynaresume.services de version différente ont été lancée. Elle montre que le service UserService est consommée, ce qui signifie que c’est le bundle de version 1.0.1 qui a pris la main.

Tappez ss pour afficher la liste des bundles et leur ID :

...
38 ACTIVE org.dynaresume.services_1.0.1.qualifier
39 ACTIVE org.dynaresume.services_1.0.0.qualifier

Nous allons maintenant arrêter le bundle org.dynaresume.services de version 1.0.1. Pour cela tapez dans la console OSGi stop 38 :

Stop Bundle [org.dynaresume.services]
osgi> --- Get UserService from OSGi services registry with ServiceTracker ---
Cannot get UserService=> UserService is null!

Cette trace montre que le service UserService n’est plus disponible. Le bundle org.dynaresume.services de version 1.0.1 étant stoppé, il ne fournit plus le service UserService. Cependant le bundle (avec le bug) org.dynaresume.services de version 1.0.0 est activé et on s’attend à ce que le service soit fournit par ce bundle. Mais ceci ne fonctionne pas? Je pense que le problème vient du fait que l’interface UserService est contenu dans les 2 versions des bundles org.dynaresume.services, ce qui au passage n’est pas très propre. Pour résoudre le problème, nous allons scinder le bundle org.dynaresume.services en 2 bundles services API et Implémentation.

II .Bundle Services API/Implémentation

Jusqu’a maintenant le bundle org.dynaresume.services contient :

  • l’API de services représenté par l’interface UserService.
  • l’implémentation de l’API de services UserServiceImpl.

A ce stade l’API et l’implémentation de services sont stockés dans le même bundle et ce choix de conception est contraignant :

  • impossible de faire cohabiter plusieurs bundles org.dynaresume.services de différentes versions (voir explication ci-dessus).
  • le cycle de vie de l’interface UserService et de l’implémentation UserServiceImpl peuvent être différent (ex : la correction d’un bug de la classe UsertServiceImpl nécessite la création du bundle services avec une nouvelle version alors que l’interface UserService ne bouge pas).
  • un intérêt de découpler les bundles API et implémentation est de permettre de fournir plusieurs implémentations d’une même API. En effet avec l’utilisation du registry de services OSGi un autre bundle peut ajouter une autre implémentation du service UserService. Ce bundle pour fonctionner doit être lié au bundle actuel org.dynaresume.services qui fournit l’API des services, mais qui enregistre aussi l’implémentation UserServicesImpl. Il n’est donc pas possible d’utiliser l’implémentation du service UserService de ce bundle sans devoir arrêter le bundle org.dynaresume.services (pour désenregsitrer UserServiceImpl) ou faire un filtre sur le service lors de sa récupération par le bundle client.

En règle général, il est conseillé de séparer l’API Services et son Implémentation en 2 bundles distincts. Nous allons dans notre cas

  • modifier le bundle org.dynaresume.services pour qu’il ne contienne que l’interface UserService (API)
  • créer un nouveau bundle org.dynaresume.services.impl qui contiendra l’implémentation UserServiceImpl et qui enregsitrera cette instance dans le registre de services OSGi via ServiceTracker.

Voici un schéma de ce que nous allons effectuer dans cette section :

Ce schéma met en évidence les 2 bundles Services API org.dynaresume.services et Implémentation.

Vous pouvez télécharger org.dynaresume_step5-api-services-bundle.zip qui contient le code expliqué ci-dessous. Nous allons partir des projets du billet précédant org.dynaresume_step4-servicetracker.zip.

II-A . Bundle Implémentation – org.dynaresume.services.impl

Ici nous allons créer le bundle org.dynaresume.services.impl, bundle d’implémentation de services. Pour cela :

  • copiez le projet org.dynaresume.services et nommez le nouveau projet org.dynaresume.services.impl.
  • Modifiez le Bundle-SymbolicName du MANIFEST.MF avec org.dynaresume.services.impl :
    Bundle-SymbolicName: org.dynaresume.services.impl
  • Supprimez l’interface org.dynaresume.services.UserService.
  • le bundle d’implémentation de services ne doit d’exposer aucun package (car il s’occupe d’enregistrer l’implémentation de services UserServiceImpl dans le registre de services OSGi). Pour cela, supprimez dans le MANIFEST.MF, la section
    Export-Package: org.dynaresume.services
  • le bundle doit faire référence au bundle domain et services API. Pour cela modifiez le MANIFEST.MF avec Require-Bundle :
    Require-Bundle: org.dynaresume.domain;bundle-version="1.0.0",
    org.dynaresume.services;bundle-version="1.0.0"

II-B .Bundle API – org.dynaresume.services

Ici nous allons modifier le bundle org.dynaresume.services, bundle d’API de services. Pour cela :

  • Supprimer la classe ServicesFactory.
  • Supprimer la classe UserServiceImpl.
  • Supprimer l’enregistrement du service dans l’Activator ce qui donne :
    package org.dynaresume.services.internal;
    
    import org.osgi.framework.BundleActivator;
    import org.osgi.framework.BundleContext;
    
    public class Activator implements BundleActivator {
    
      public void start(BundleContext context) throws Exception {
        System.out.println("Start Bundle [" + context.getBundle().getSymbolicName() + "]");
      }
    
      public void stop(BundleContext context) throws Exception {
        System.out.println("Stop Bundle [" + context.getBundle().getSymbolicName() + "]");
      }
    
    }

Relancez (via OSGi DynaResume) Equinox et la console OSGi affiche :

osgi> Start Bundle [org.dynaresume.domain]
Start Bundle [org.dynaresume.services]
Start Bundle [org.dynaresume.services.impl]
Start Bundle [org.dynaresume.simpleosgiclient]
--- Get UserService from OSGi services registry with ServiceTracker ---
User [login=angelo, password=]
User [login=djo, password=]
User [login=keulkeul, password=]
User [login=pascal, password=]

Cette trace montre que les 4 bundles sont lances dont les bundles API et Implémentation services. La consommation du service UserService s’effectue ensuite correctement par le bundle client.

III .Services Impl& version bundle

Maintenant que nous avons 2 bundles services API et Implémentation, nous allons recommencer notre test avec nos bundles services buggués et patchés en repartant des projets org.dynaresume_step5-api-services-bundle.zip. Vous pouvez télécharger org.dynaresume_step5-api-services-bundle-patch.zip qui contient le code expliqué ci-dessous.

III-A .Bundle org.dynaresume.services.impl (1.0.1)

  • Copiez le projet org.dynaresume.services.impl et nommez le org.dynaresume.services.impl_patch.
  • Modifier la version du bundle en 1.0.1.qualifier dans le MANIFEST.MF :
    Bundle-Version: 1.0.1.qualifier

III-B .Bundle org.dynaresume.services.impl (1.0.0)

Modifiez la classe UserServiceImpl comme ceci :

public Collection<User> findAllUsers() {
  throw new RuntimeException("Error into findAllUsers method.");
}

Relancez (via OSGi DynaResume) Equinox et la console OSGi affiche une exception toutes les 5 sec en appelant UserService :

--- Get UserService from OSGi services registry with ServiceTracker---
java.lang.RuntimeException: Error into findAllUsers method.
at org.dynaresume.services.impl.UserServiceImpl.findAllUsers(UserServiceImpl.java:22)
at org.dynaresume.simpleosgiclient.internal.FindAllUsersThread.displayUsers(FindAllUsersThread.java:74)
at org.dynaresume.simpleosgiclient.internal.FindAllUsersThread.run(FindAllUsersThread.java:40)

Ceci permet d’affirmer que le bundle (qui est buggé) de version 1.0.0.qualifier est utilisé.

Tappez ss :

...
39 ACTIVE org.dynaresume.services.impl_1.0.0.qualifier
40 ACTIVE org.dynaresume.services.impl_1.0.1.qualifier
...

Ceci montre que les 2 bundles services sont lancés en même temps et qu’il est possible de faire cohabiter plusieurs versions de bundles. Le bundle de version 1.0.0.qualifier est utilisé car il est lancé avant celui de version 1.0.1.qualifier. L’ordre de lancement des bundles est important. Le conteneur OSGi n’utilise pas l’information « version » pour savoir quel bundle doit être utilisé en priorité.

Arrêter le bundle de version 1.0.0.qualifier avec la commande (dans mon cas) stop 39. La console OSGi affiche :

--- Get UserService from OSGi services registry with ServiceTracker ---
User [login=angelo, password=]
User [login=djo, password=]
User [login=keulkeul, password=]
User [login=pascal, password=]

Cette trace permet d’affirmer que le bundle services patch (version 1.0.1) est maintenant utilisé. Ce scénario permet de montrer comment il est facile de fournir un livrable unitaire (bundle) et de l’installer sans perturber les utilisateurs connectés à l’application. L’arrêt/stop de bundles est extrêmement intéresant :

  • en développement dans le cas d’une grosse application qui mets un temps (parfois considérable) à démarrer. En effet avec OSGi, cela ne nécéssite pas de redémarrer le serveur pour tester uniquement le service en cours de développement.
  • en production dans le cas d’une livraison d’un distribuable qui corrigerait un bug (comme ce que nous avons expliqué ci-dessus) et qui évite de pénaliser toute les utilisateurs connectés sur l’application.

IV .Import package vs Require Bundle

Il existe plusieurs manières de gérer les dépendances entre les bundles OSGi dont :

  • Require Bundle. ce type de dépendance est lié à un bundle donné. Elle permet ensuite d’utiliser tous les packages exportès du bundle dans le bundle qui y fait référence. C’est ce type de dépendance utilisé dans les Plugin Eclipses.
  • Import Package : ce type de dépendance n’est pas lié à un bundle donné. Elle permet d’indiquer que l’on souhaite utiliser les classes stockés dans un package donné. Ce package donné est un package exporté par un bundle dont on ne connait pas son existence.

OSGi préconnise d’utiliser Import Package pour ne pas lier fortement les bundles entre eux. Ce type de dépendance permet ensuite de scinder un bundle A en plusieurs bundles (A1, A2, etc) sans que les autres bundles qui étaient lié à A soient impactés. Il existe cependant 2 inconvéniants avec Import Package par rapport à Require Bundle :

  • les packages doivent être importés explicitement. Si un bundle que l’on souhaite dépendre expose un nombre considérable de packages, ceci nécéssitera d’importer un à un les packages alors qu’avec Require Bundle le problème ne se pose pas.
  • split package : si 2 bundles exportent 2 packages identiques, rien ne garantit que le bundle qui importe le package utilise la classe que l’on attend.

Pour éviter les 2 problèmes avec Import Package, les Plugin Eclipse utilisent Require Bundle car il n’y a aucune maîtrise des plugins qui sont lancés (on peut ajouter ses propres plugins avec n’importe quel package). Nous avons utilisé jusqu’à maintenant le type de dépendance Require Bundle que nous allons supprimer pour utiliser Import Package (nous maitrisons le choix des bundles à lancer). Vous pouvez télécharger org.dynaresume_step5-import-package.zip qui contient le code expliqué ci-dessous :

  • Bundle org.dynaresume.services:
    • Supprimmez la dépendances au bundle org.dynaresume.domain en supprimant la méta donnée Require-Bundle du MANIFEST.MF.
    • Importer le package org.dynaresume.domain :
      Import-Package: org.dynaresume.domain,
      org.osgi.framework;version="1.3.0"
  • Bundle org.dynaresume.services.impl:
    • Supprimmez les dépendances les 2 bundles org.dynaresume.domain et org.dynaresume.services en supprimant la méta donnée Require-Bundle du MANIFEST.MF.
    • Importer le package org.dynaresume.domain et org.dynaresume.services:
      Import-Package: org.dynaresume.domain,
      org.dynaresume.services,
      org.osgi.framework;version="1.3.0"
  • Bundle org.dynaresume.simpleosgiclient:
    • Supprimmez les dépendances les 2 bundles org.dynaresume.domain et org.dynaresume.services en supprimant la méta donnée Require-Bundle du MANIFEST.MF.
    • Importer le package org.dynaresume.domain et org.dynaresume.services:
      Import-Package: org.dynaresume.domain,
      org.dynaresume.services,
      org.osgi.framework;version="1.3.0",
      org.osgi.util.tracker;version="1.4.2"

V .Conclusion

Dans ce billet nous avons mis en évidence et montrer l’interêt d’utiliser le registre de services OSGi (pour arrêter/stopper les bundles). Dans le prochain billet nous montrerons l’interêt d’utiliser Spring et plus particulièrement Spring DM pour pouvoir déclarer les services que l’on souhaite fournir/consommer via le registre de services OSGi.

Vous pouvez lire le billet suivant [step6].

Catégories :DynaResume, OSGi
  1. nsoufiane
    mars 19, 2010 à 11:57

    je tiens a vous remercié pour l’effort fournie et pour la qualité de votre travail .

    • mars 19, 2010 à 1:14

      Bonjour,

      Merci beaucoup de vos encouragements. J’espère que ma série de billet pourra vous aider. Si vous avez des remarques sur certains points que j’ai expliqué dans les billets n’hésitez pas. J’en tiendrai compte et essaierait de modifier les billets en conséquence.

      Angelo

  2. Pierre
    mars 5, 2011 à 3:05

    Bonjour,

    Encore bravo pour votre présentation.

    Dans le présent billet, le 5e, il me semble qu’il serait utile de préciser que le bundle d’implémentation des services devait être ajouter « à la main » dans la liste des bundles de la configuration de lancement. « Add Required Bundles » n’a aucun moyen de savoir quelle est l’implémentation à utiliser. Néophyte en matière de développement de plugins, j’ai mis un certain temps à le comprendre.

    Pierre

  1. novembre 17, 2009 à 10:39
  2. novembre 29, 2009 à 6:38
  3. décembre 15, 2009 à 9:55
  4. décembre 22, 2009 à 2:22

Laisser un commentaire