Interopérabilité des services Web de type « Contract First » (priorité au contrat) entre Microsoft .NET et IBM WebSphere Eric Cherng Vertigo Software, Inc. James Duff Vertigo Software, Inc. Dino Chiesa Microsoft Corporation 28 octobre 2004 Public visé : Architectes et développeurs Télécharger l'exemple d'interopérabilité pour cet article Introduction Contenu de l'exemple d'interopérabilité Conditions et configuration logicielle requises Pourquoi des services Web ? Service Web 1 : Addition Service Web 2 : Évaluations et rapports Service Web 3 : Recherche de produits Conclusion Recommandations/Astuces Remerciements Liens utiles Introduction Tandis que les services Web attirent de plus en plus d’adeptes, les fournisseurs s’efforcent d’ajouter de nouveaux standards et fonctionnalités dans leurs infrastructures pour enrichir et consolider la communication entre systèmes. Les organisations, qui investissent de plus en plus de temps et d’argent dans la recherche de l’optimisation des services Web et de leurs technologies de base, doivent connaître les forces et les limites de ces technologies, notamment en matière de savoir-faire du développeur, de facilité de maintenance et d’interopérabilité. Le présent article est consacré à l’interopérabilité ; il définit comme point de départ l’interopérabilité entre Microsoft .NET Framework et IBM WebSphere via les services Web. L’objectif de ce livre blanc et de l’exemple qui l’accompagne est d’expliquer aux développeurs de chaque plate-forme comment intégrer l’autre plate-forme. Bien que notre étude soit focalisée sur les plates-formes .NET de Microsoft et WebSphere d’IBM, les exemples illustrent des techniques et principes de base pouvant être réutilisés dans tous les projets exigeant une interopérabilité entre plates-formes via des services Web. Contenu de l’exemple d’interopérabilité Cet exemple contient : 1. L’exemple de code de service Web : Il s'agit de services Web et de clients des services Web écrits en Java et en C# pour trois interfaces de service différentes : 1. Service Web 1 : il s'agit d'un service Web effectuant une simple addition dont l’objectif est de vous familiariser avec les outils et les plates-formes utilisés. 2. Service Web 2 : ce service montre comment faire passer des types d’objet complexes d’une plate-forme à l’autre. 3. Service Web 3 : ce service met en évidence un modèle de conception pour créer des services Web extensibles, capables de gérer les modifications dans les mappages de données. Chaque service Web fait preuve d’une interopérabilité hétérogène. Il suffit de modifier un paramètre sur le client du service Web pour que celui-ci puisse communiquer soit avec le service Web .NET, soit avec le service WebSphere. Dans cet exemple simple, il n’est pas nécessaire d’avoir accès à la base de données pour exécuter un service Web et un client. Dans ce document, nous étudierons en détail la création du service Web 1 à partir des principes de base. Nous n’approfondirons pas autant l’analyse des services Web 2 et 3. 2. Ce livre blanc, qui décrit en détail les différents services Web et leur conception. Les techniques et concepts abordés dans cet article sont généraux et peuvent donc s’appliquer à toute connexion entre deux plates-formes de fournisseurs différents. Néanmoins, les exemples ont été développés et testés uniquement avec les kits de développement (SDK) de services Web de .NET Framework et d’IBM WebSphere. Conditions et configuration logicielle requises Cet article sur l’interopérabilité (et l’exemple associé) considère que le lecteur connaît les bases de .NET Framework, d’ASP.NET et du serveur d’application WebSphere d’IBM. Le service Web 1 s’adresse aux développeurs qui n’ont jamais créé de services Web ou qui ne connaissent pas l’une des deux plates-formes. Même si vous êtes familiarisé avec les services Web et avec les deux plates-formes en question, nous vous conseillons de consulter au moins la partie concernant le service Web 1 car elle contient des trucs et astuces utiles qui ne figurent pas dans les sections traitant des services Web 2 et 3. Configuration logicielle requise Nous avons utilisé les logiciels suivants pour créer et tester les exemples. Microsoft Windows XP Professionnel SP1 Microsoft Internet Information Services 5.1 Kit de développement (SDK) de Microsoft .NET Framework, version 1.1 SDK de Sun Java, version 1.4 Kit de développement pour services Web de WebSphere (WSDK), version 5.1 Kit des développeurs de services Web Java (JWSDP), version 1.3 Si vous n’avez aucun logiciel Java, vous devez impérativement installer les logiciels requis dans l’ordre suivant : 1. SDK Sun Java 1.4 2. Eclipse 2.1.3 3. WSDK 5.1 4. JWSDP 1.3 5. Eclipse nécessite le SDK Java, lequel est inclus dans le WSDK mais pour installer le WSDK, Eclipse doit déjà être installé ! 6. Vous trouverez des informations complémentaires sur l’installation dans le fichier README à télécharger avec les exemples d’interopérabilité. Pourquoi des services Web ? Les services Web en tant que technologie applicative existent depuis de nombreuses années. Bien avant que les organisations et les sociétés ne créent les standards de services Web, les analystes d’entreprise, les architectes et les ingénieurs en logiciel avaient compris que les données d’entreprise étant dispersées sur de nombreux systèmes, il fallait que ces derniers puissent communiquer entre eux. Les premières tentatives de liaison des applications entre elles à l’aide des technologies d’appel de procédure distante (RPC), comme RMI, DCOM et d’autres mécanismes d’interconnexion spécifiques à certaines plates-formes, ont généralement échoué en raison de la diversité des fournisseurs et des plates-formes utilisées dans les organisations. Ces approches ont aussi avorté parce que leur usage n’était pas adapté à Internet, d’où la lenteur, voire l’absence de réponses sur ce réseau. D’autres solutions, qui utilisaient les files d’attente de messages, les verbes PUT/GET et l’ordonnancement manuel des messages, posaient des problèmes de maintenance et de productivité en matière de programmation. C’est pourquoi les développeurs ont opté pour des standards et protocoles courants : XML et HTTP. Lorsque les ingénieurs se sont lancés dans la création d’applications capables de communiquer entre elles, ils ont choisi XML parce que ce langage est utilisé et pris en charge par toutes les plates-formes. Le protocole HTTP a été retenu en raison de son utilisation étendue et de sa capacité à traverser les pare-feu. Les fournisseurs comme Microsoft ou IBM ont commencé à créer des infrastructures pour soutenir ces efforts de développement et faciliter le travail des développeurs. Pour cela, ces infrastructures éliminaient la charge que représentent la sérialisation et la désérialisation du XML et fournissaient des éléments structurels communs comme le code requis pour établir des connexions entre systèmes. La naissance des services Web a été accueillie comme une promesse de simplification de l’intégration entre systèmes et applications hétérogènes. Les fournisseurs, utilisant les services Web comme catalyseur, se rallient maintenant autour du concept d’architecture orientée services qui permet d’interconnecter des solutions individuelles, éventuellement créées (en toute légitimité) à l’aide de protocoles RPC propriétaires comme RMI ou DCOM. Les données peuvent ainsi circuler en temps réel dans toute l’entreprise. Les bénéfices et le potentiel en termes d’intégration sont donc réels, mais dans la pratique, les développeurs estiment pour la plupart que la création de services Web interopérables est plutôt complexe. La création de services Web même très simples recèle de nombreux risques comme les conflits de types ou l’absence de prise en charge de certaines fonctions. Notre premier exemple présente un service Web interopérable. Nous vous expliquerons les différentes étapes du processus de conception et de création d’un tel service. Service Web 1 : Addition Le premier exemple explique les bases d’un service Web interopérable. Il s’agit d’un service qui effectue une simple addition. Il accepte deux entiers de la part du client et renvoie la somme de ces deux nombres. Le schéma fonctionnel suivant décrit l’architecture de ce service Web : Figure 1. Architecture fonctionnelle du service Web 1. Qu’est-ce qui vient en premier, l’implémentation ou l’interface ? La technique de création de services Web la plus courante, la plus avérée et la mieux prise en charge par les outils consiste à « inférer » une interface de service Web à partir d’une implémentation existante. Le développeur rédigera le code suivant : public int Ajouter(int x, int y) { return x+y; } Dans ASP.NET, il est très simple d'exposer ce code en tant que service Web : il suffit d’ajouter au code l’attribut WebMethod. Cette technique est souvent appelée « Implementation First » (priorité à l’implémentation) ou « Code First » (priorité au code) car l’interface de service Web, décrite de façon formelle dans un document WSDL (Web Service Description Language), est dérivée de l’implémentation. Figure 2. Développement d’un service Web par la méthode « Implementation First » (priorité à l’implémentation). La technique de développement de services Web appelée « Implementation First » consiste à écrire d’abord le code du service Web (voir étape nº 1 de la figure 2). Après compilation, l’infrastructure des services Web utilise ce code pour générer de façon dynamique un fichier WSDL (étape nº 2). Lorsque les clients demandent la définition du service Web, ils récupèrent le fichier WSDL généré et créent le proxy client à partir de cette définition (étape nº 3). Par exemple, dans ASP.NET, le fichier WSDL peut être généré de façon dynamique à partir d’une implémentation avec URL comme celle-ci : http://hostname:port/Service1.asmx?WSDL Lorsque le runtime de .NET détecte le paramètre WSDL dans la requête, il effectue une réflexion sur le code doté de l’attribut WebMethod pour générer de façon dynamique une opération de description du service sous-jacent dans le fichier WSDL. Cette technique d’implémentation est très simple et fonctionne très bien, mais elle génère quelques problèmes, notamment en cas de services Web utilisés pour connecter des systèmes hétérogènes. Par exemple, si l’on commence par l’implémentation, il n'est pas possible d’inclure dans le service Web des types spécifiques à une plate-forme. Les types de jeux de données .NET et de vecteurs Java sont spécifiques à une plate-forme et sont difficiles à représenter sur d’autres plates-formes. En effet, il n’existe pas encore de mappage unique et bien défini entre ces types spécifiques et XML. Ce n’est pas parce qu’un client .NET est capable de reconnaître dans un Blob de XML un jeu de données, qu’un client de service Web écrit en Java peut faire la même chose. Nous sommes donc confrontés à des problèmes d’interopérabilité. Le schéma XML standard W3C définit des types de données intégrés dont, entre autres, les chaînes (string), les entiers de différentes tailles (integer), les variables booléennes (Boolean), les variables représentées en virgule flottante (float) en simple et double précision ou les données de date et d’heure (DateTime). Chaque plate-forme applicative prend également en charge son propre jeu de types de données. L’intersection entre ces jeux de types de données définit les types qui offriront la meilleure interopérabilité entre plates-formes. Si vous commencez par des types appartenant au schéma XML, il est facile de les mapper vers les types spécifiques à la plate-forme, mais si vous commencez par ces derniers, le mappage inverse n’est pas forcément possible. Par exemple le mappage des types integer, string, Boolean et float du schéma XML vers les types de données correspondant dans .NET ou Java est facile. En revanche, les types de vecteurs (Vector) ou de tables de hachage (Hashtable) sont natifs des différentes plates-formes et ne font donc pas partie des types officiels du schéma XML. Pour plus d’informations sur les types de données pris en charge, consultez les spécifications (en anglais) des types de données du schéma XML. Les runtimes de la plupart des services Web (y compris le runtime intégré à .NET Framework et à WSDK) sont capables d’effectuer un mappage entre les primitives du schéma XML et les primitives spécifiques à la plate-forme. Ainsi, une chaîne du schéma XML sera mappée vers le type System.String dans .NET et vers le type java.lang.String dans Java. Avec les primitives du schéma XML et les structures et tableaux générés à partir de ces primitives, il est possible de créer des types de données plus complexes, décrits dans le schéma XML, qui pourront être mappés de façon très fidèle d’une plate-forme à l’autre. Ces types de données peuvent alors figurer dans les documents WSDL à utiliser dans le service Web. Cela représente l’essence de la méthode appelée « WSDL first » (priorité au WSDL) : si vous utilisez les types du schéma XML pour définir les types de données utilisés dans le service Web, vous avez plus de chance de pouvoir mapper ces types de données entre plates-formes. Cette technique qui consiste à créer d’abord le fichier WSDL est aussi appelée parfois « Schema First » (priorité au schéma). Si ces deux appellations sont généralement employées indifféremment, elles renvoient en fait à deux concepts légèrement différents. Dans cet article, nous souhaitons amener les architectes et les développeurs à créer le contrat à partir des définitions WSDL, avant de créer le code sous-jacent du service. Pour créer le fichier WSDL, les développeurs peuvent soit créer des définitions de schéma XML spécifiques à l’interface avec laquelle elles seront utilisées (« WSDL First »), soit utiliser les schémas XML déjà définis dans leur domaine d’application (« Schema First »). Cet article utilisera la terminologie du concept « WSDL First ». Figure 3. Développement d’un service Web par la méthode « WSDL First » (priorité au WSDL). Le problème avec la technique consistant à créer d’abord le fichier WSDL, c’est que les outils produits aujourd’hui ne favorisent pas cette pratique. Elle reste néanmoins faisable, mais difficile à implémenter. Visual Studio comporte un éditeur de schéma et un éditeur XML mais pas d’éditeur WSDL. Eclipse ne propose pas non plus d’éditeur WSDL. Heureusement, ces deux environnements offrent une fonctionnalité permettant de générer le squelette de code du service Web en plus du code du proxy client à partir d'un fichier WSDL. Vous pouvez utiliser l’outil de votre choix pour créer votre propre fichier WSDL, y compris VI ou le Bloc-notes. Plutôt que d’éditer directement le texte, vous pouvez aussi utiliser pour plus de commodité des outils spécialisés comme XmlSpy d’Altova, dotés de concepteurs WSDL. Toutefois, cette solution n’est pas forcément appropriée dans la mesure où de nombreux développeurs restent incapables de « penser en WSDL ». Il existe une solution à ce problème : concevoir rapidement un prototype d’interface de service Web en utilisant la technique « Implementaiton First ». Il suffit d’utiliser les fonctions de génération dynamique de fichier WSDL d’ASP.NET ou du WSDK d’IBM pour créer un fichier modèle WSDL. Il devient alors possible d'entreprendre le développement selon la méthode « WSDL First » et de peaufiner l’interface à sa guise. Ce processus s’exécute de façon itérative jusqu’à obtention du fichier WSDL final. Comme l’illustre la figure 3 ci-dessus, la création de services Web à partir de la méthode « WSDL First » comporte trois étapes majeures : 1. Créer le fichier WSDL. 2. Implémenter le document WSDL. a. Créer le service Web. b. Créer le client du service Web. Vous pouvez effectuer les sous-étapes A et B de l’étape 2 dans l’ordre de votre choix. Comme elles dépendent toutes deux du document WSDL, ce qui importe, c’est de créer le document WSDL avant d’entreprendre A ou B. Dans la suite de cette section, nous étudierons les étapes du développement du premier exemple de service Web selon le processus « WSDL First ». Vous trouverez le code source complet de cet exemple dans le téléchargement qui accompagne cet article. Création du fichier WSDL à partir de Visual Studio .NET (« Implementation First ») Avant d’aborder la création de services Web à partir de l'option « WSDL First », qui consiste à générer d’abord le fichier WSDL, nous étudierons le modèle de développement « Implementation First » de Visual Studio .NET, qui accorde la priorité à l’implémentation. Nous avons choisi de commencer par cette méthode car la création d'un fichier WSDL en partant de zéro est une tâche difficile. Il est beaucoup plus simple avec les outils dont vous disposez de prendre un raccourci et d’utiliser le fichier WSDL généré par l’approche « Implementation First ». Imaginez qu’il vous faille entrer manuellement le code suivant : <?xml version="1.0" encoding="utf-8"?> <definitions xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:s0="http://example.org/" targetNamespace="http://example.org/" xmlns="http://schemas.xmlsoap.org/wsdl/"> <types> <s:schema elementFormDefault="qualified" targetNamespace="http://example.org/"> <s:element name="Add"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="x" type="s:int" /> <s:element minOccurs="1" maxOccurs="1" name="y" type="s:int" /> </s:sequence> </s:complexType> </s:element> <s:element name="AddResponse"> <s:complexType> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="AddResult" type="s:int" /> </s:sequence> </s:complexType> </s:element> </s:schema> </types> <message name="AddSoapIn"> <part name="parameters" element="s0:Add" /> </message> <message name="AddSoapOut"> <part name="parameters" element="s0:AddResponse" /> </message> <portType name="Service1Port"> <operation name="Add"> <input message="s0:AddSoapIn" /> <output message="s0:AddSoapOut" /> </operation> </portType> <binding name="Service1Soap" type="s0:Service1Port"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" /> <operation name="Add"> <soap:operation soapAction="http://example.org/Add" style="document" /> <input> <soap:body use="literal" /> </input> <output> <soap:body use="literal" /> </output> </operation> </binding> <service name="Service1"> <port name="Service1Soap" binding="s0:Service1Soap"> <soap:address location="http://localhost/NetWSServer/Service1.asmx" /> </port> </service> </definitions> Si pour le développeur WSDL chevronné, cet exercice peut sembler banal, pour le reste d’entre nous, il supposerait d’ingurgiter les 51 pages de spécifications WSDL. C’est pourquoi nous utiliserons au démarrage la méthode commençant par l’implémentation. Pour les développeurs, la création d'un service Web en rédigeant du code est en effet la technique la plus simple. Commencez par créer un projet de service Web ASP.NET en C# dans Visual Studio .NET. Appelez ce projet WebService1WSDL. Figure 4. Boîte de dialogue Nouveau projet dans Visual Studio .NET. Ensuite, ouvrez le code du fichier Service1.asmx. Supprimez les commentaires de la méthode HelloWorld et remplacez cette méthode par la suivante : [WebMethod] public int Ajouter(int x, int y) { return -1; } Figure 5. Code d’un service Web selon la méthode « Implementation First ». Créez le projet et vérifiez qu’il n’y a pas d’erreur. Puis, cliquez avec le bouton droit sur Service1.asmx et configurez cette page comme page de démarrage. Vous pouvez maintenant appuyer sur F5 pour afficher la page de test du service Web (figure 6). Figure 6. Page de test du service Web. Soulignons que ce soit le fichier ASMX qui constitue le véritable service Web. La page que vous visionnez est générée par l’infrastructure pour documenter le service Web et pour permettre au développeur de tester ce service sans avoir à créer manuellement une application cliente. La fonctionnalité de test n’est accessible que si vous affichez cette page localement ; elle ne fonctionne pas pour les services Web utilisant comme paramètres d’entrée des types de données complexes. Pour des raisons de sécurité, pensez à désactiver cette page de test après avoir déployé votre service Web sur une machine de production. Cliquez ensuite sur le lien Service Description (Description du service) situé en haut à droite de la page. Cette action ouvre une autre page Web affichant le WSDL généré pour votre service. La fonction de génération du WSDL reste disponible tant que vous ne la désactivez pas délibérément. Enregistrez le fichier WSDL sur votre disque local et nommez-le WebService1.wsdl. Figure 7. Fichier WSDL généré par Framework. Vous venez de créer un fichier WSDL sans avoir eu à apprendre les spécifications WSDL ! Autre conseil important ici : étant donné que nous avons généré le fichier WSDL à partir de ce projet temporaire, l’emplacement du service Web dans le document WSDL est préprogrammé de façon à pointer vers ce projet temporaire. Cela n’a aucune incidence sur le service Web en luimême, mais les clients qui utilisent ce fichier WSDL utiliseront cette référence comme emplacement du service Web. Il est donc important de modifier cette valeur avant de déployer le fichier WSDL sur le site Web. Nous changerons cette référence plus tard, dès que nous connaîtrons l’emplacement réel de notre service Web. Si vous avez oublié de modifier cet emplacement et que les applications clientes ont déjà été créées, vous pouvez toujours corriger la référence en modifiant l’emplacement vers lequel pointent les références des clients. Dans .NET et dans WebSphere, les proxys clients de services Web permettent de configurer correctement la propriété d’URL. Cela s’avère également utile lorsque vous passez du développement à la production et que vous ne souhaitez modifier que le point de terminaison du service. Comme le service Web que nous créons est simple, le fichier WSDL n’a pas besoin d’être peaufiné. L’étape suivante consiste à créer le service Web .NET. Implémentation du service Web .NET (« WSDL First ») À partir du fichier WSDL généré à l’étape précédente, nous allons maintenant créer un nouveau service Web .NET. Pour passer du fichier WSDL à un fichier de code source, nous utiliserons une application console appelée wsdl.exe pour générer le code. Cet outil analyse le fichier WSDL et tous les autres fichiers externes pour créer un fichier de code source unique contenant toutes les classes, méthodes et types requis pour implémenter le service Web. Wsdl.exe est installé avec Visual Studio .NET 2003 (et le SDK de .NET). Pour utiliser cet outil, vous devez ouvrir l’invite de commande de Visual Studio .NET 2003, située par défaut dans le menu Démarrer, sous Tous les programmes/Microsoft Visual Studio .NET 2003/Outils Visual Studio .NET, puis aller à l’endroit ou vous avez enregistré le fichier WSDL. Pour que wsdl.exe génère le fichier source du service Web, exécutez la commande suivante. wsdl.exe /server WebService1.wsdl Figure 8. Invite de commande de Visual Studio .NET 2003 exécutant wsdl.exe. Notez que l’utilitaire génère un message indiquant que le fichier Service1.cs a été créé. Ce fichier sera le point de départ de notre service Web. Le fichier généré par wsdl.exe n’est qu’un modèle de la méthode que nous souhaitons implémenter ; il doit donc être modifié pour fonctionner correctement. La commande wsdl.exe active génère toujours une classe abstraite pour le service Web lorsqu’elle est exécutée avec l’option /server. Nous la convertirons en classe concrète en éliminant le mot clé abstract et en fournissant une implémentation pour la méthode Add. Nous placerons également la classe dans l’espace de noms WebService1. Nous obtiendrons le code suivant : namespace WebService1 { /// <remarks/> [System.Web.Services.WebServiceBindingAttribute(Name="Service1Soap", Namespace="http://tempuri.org/")] public class Service1 : System.Web.Services.WebService { /// <remarks/> [System.Web.Services.WebMethodAttribute()] [System.Web.Services.Protocols.SoapDocumentMethodAttribute( "http://tempuri.org/Add", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle= System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] public int Add(int x, int y) { int result; result = x + y; return result; } } } Peut-être vous demandez-vous pourquoi ne pas simplement sous-classer la classe abstraite générée au lieu de la convertir en classe concrète ? Il existe une bonne raison. L’outil wsdl.exe génère du code doté d’attributs utilisés par le sérialiseur XML de .NET et le runtime des services Web pour effectuer le mappage des objets vers le XML et inversement. Dans notre exemple, un attribut utilise l’espace de noms http://tempuri.org pour le document XML généré. Ce type d'attribut n'est pas hérité par les sous-classes. Par conséquent, si nous sous-classions la classe abstraite, il nous faudrait couper-coller tous ces attributs dans notre classe concrète. Plutôt que de dupliquer manuellement tous ces attributs, il est plus simple de modifier le fichier directement. Bien sûr, cela signifie que si le fichier WSDL est modifié, il vous faudra générer à nouveau le code source du fichier WSDL. Il convient d’effectuer cette tâche avec soin pour éviter d’effacer tout le code existant. Vous devrez copier manuellement dans le nouveau fichier le code d’implémentation du service Web figurant dans l’ancien fichier. Maintenant que le code source de notre service Web est prêt, nous pouvons créer notre solution Visual Studio. Créez dans Visual Studio .NET un nouveau projet de service Web ASP.NET en C# et appelez le WebService1. Cela fait, copiez le fichier Service1.cs créé précédemment et contenant le code d’implémentation du service Web dans le répertoire contenant les fichiers du service Web générés par Visual Studio. Il s’agit généralement de c:\inetpub\wwwroot\WebService1. Figure 9. Affichage dans l’Explorateur Windows des fichiers du projet de service Web. L’exemple de service Web Service1.asmx est généré en tant qu’élément de l’Assistant de projet Visual Studio. Puisque la commande wsdl.exe ne génère que le code d’implémentation du modèle et non le fichier d’entrée (ASMX), nous préférons réutiliser le fichier Service1.asmx généré par Visual Studio plutôt que d’en créer un nous-même. Cependant, il existe déjà un fichier source correspondant au fichier Service1.asmx. Pour associer le fichier ASMX à notre code d’implémentation, il suffit d’effacer le code d’implémentation généré par Visual Studio (Service1.asmx.cs) et d’attribuer le nom de ce fichier à notre code d’implémentation. Pour cela, vérifiez que les fichiers Service1.asmx et Service1.asmx.cs ne sont pas ouverts dans Visual Studio, puis supprimez le fichier Service1.asmx.cs et attribuez le nom Service1.asmx.cs au fichier Service1.cs. Pour vérifier que la transplantation du code a bien fonctionné, sélectionnez Service1.asmx dans l’Explorateur de solutions de Visual Studio et cliquez sur le menu Afficher, puis sur Code. Le code du modèle doit s’afficher et l’implémentation de la méthode Add que nous avons modifiée précédemment doit apparaître dans Visual Studio .NET. Enfin, pour tester le bon fonctionnement du service, cliquez avec le bouton droit sur Service1.asmx puis cliquez sur Définir comme page de démarrage. Allez ensuite dans le menu Déboguer et cliquez sur Démarrer pour créer le projet et ouvrir Service1.asmx dans votre navigateur. Vérifiez que le service Web fonctionne correctement à l’aide de la page de test. Figure 10. Page de test du service Web d’addition. Figure 11. Réponse du service Web à la requête en ajoutant 12 et 45. Pour empaqueter le service Web .NET, il nous faut maintenant retourner au document WSDL d'origine pour effectuer quelques modifications mineures. Comme nous l’avons déjà remarqué, le document WSDL pointe actuellement vers notre service Web de projet temporaire. Maintenant que nous avons terminé la création du service Web réel, nous pouvons modifier la référence de localisation de façon à ce qu’elle pointe vers ce service. Pour cela, ouvrez le document WSDL d'origine et remplacez l’attribut location de l’élément soap:address pour qu’il pointe vers ce nouveau service Web. Le nouvel élément soap:address doit se présenter comme suit : <port name="Service1Soap" binding="s0:Service1Soap"> <soap:address location="http://localhost/NetWSServer/Service1.asmx" /> </port> Figure 12. Modification de l’emplacement du service Web dans le fichier WSDL. La création d’un service Web .NET selon la méthode « WSDL First » est maintenant terminée. Nous allons à présent créer le client de service Web WSDK qui utilisera ce service Web .NET. Implémentation du client de service Web WSDK Maintenant que notre service Web est implémenté, nous allons créer un client pour l’utiliser. Comme le sujet de cet article est l’interopérabilité, nous utiliserons le service Web .NET en exécutant une page JSP sur le serveur d’application WebSphere d’IBM. Démarrez Eclipse et créez un nouveau projet Web Dynamique (Dynamic Web Project). Appelez ce projet WebServiceClient1 et le fichier de projet EAR WebServiceClient1Ear. Figure 13. Affichage dans Eclipse du projet WebServiceClient1. Ensuite, ajoutez le fichier WSDL au projet WebServiceClient1. Pour cela, il suffit de faire glisser le fichier directement dans le projet situé dans le volet de navigation d’Eclipse. Nous souhaitons maintenant générer un proxy Java pour le service Web. De même que Visual Studio .NET crée une classe proxy lorsqu’une référence Web est ajoutée à un projet, les outils WSDK vont créer un jeu de fichiers Java qui permettront d’appeler le service Web plus facilement que s’il fallait intervenir directement sur les échanges réseau. Puisque nous avons déjà ajouté le document WSDL au projet, vous pouvez créer le proxy en cliquant avec le bouton droit sur le document WSDL, en pointant sur le menu Web Services (Services Web) et en cliquant sur Generate Client (Générer le client). Cela ouvrira l’Assistant WSDL to Java Bean Proxy (WSDL vers proxy Java Bean) illustré à la figure 14. Figure 14. Assistant WSDL to Java Bean Proxy. Dans la première boîte de dialogue, pensez à cocher la case Test the generated proxy (Tester le proxy généré). Cela ouvrira la page JSP (similaire à la page de test générée à partir de Service1.asmx à la création d’un service Web dans Visual Studio) qui servira à vérifier les classes proxy générées. Pour les autres options de l’Assistant, les valeurs par défaut conviennent. Vous pouvez donc cliquer sur Finish (Terminer). Dès qu’Eclipse a terminé de générer tous les fichiers nécessaires, la page de test suivante s’affiche dans la fenêtre principale d’Eclipse. Figure 15. Affichage dans l’IDE d’Eclipse de la page de test JSP. Cliquez sur add(int,int) dans le volet Methods (Méthodes) et indiquez deux nombres à additionner dans le volet Inputs (Entrées). La somme de ces deux nombres doit s’afficher dans le volet du bas Result (Résultat). En arrière-plan, la page JSP appelle la classe proxy du service Web, générée précédemment par l’Assistant WSDK du service Web, pour appeler notre service Web .NET. Étant donné que nous avons modifié la référence d'emplacement du service Web dans le document WSDL, notre client Java sait exactement où trouver le service Web que nous avons créé lors de l’étape 2. Si la somme de vos deux nombres est correcte, cela démontre que le proxy Java a été généré correctement et qu'il communique déjà avec le service Web .NET. Il existe une autre méthode pour vérifier la connexion entre un service et un client. Elle consiste à définir un point d’arrêt dans le code du service Web de Visual Studio .NET puis à exécuter le service Web en mode de débogage. Côté Java, si vous exécutez la fonction d’addition dans la page JSP, Visual Studio .NET devrait normalement interrompre l’exécution du service et s’arrêter au point que vous avez défini. À ce stade, nous avons vérifié le bon fonctionnement de la classe de proxy Java générée par le WSDL. Nous avons également utilisé la page de test générée par l’Assistant WSDK pour tester les appels vers le service Web .NET à partir de Java. Puisque ces deux points fonctionnent bien, la démo concernant le service Web .NET et le client de service Web Java pour le service Web 1 est maintenant terminée. Si nous n’avons pas décrit en détail le processus de création d’un client de service web .NET et du serveur de service Web Java, l’exemple de code accompagnant cet article inclut des implémentations de serveurs et de clients dans .NET et dans Java. Si vous souhaitez les générer vous-même, vous trouverez d’autres didacticiels concernant ces procédures. Service Web 2 : Évaluations et rapports Le deuxième exemple repose sur le premier pour créer un service Web utilisant un schéma de données beaucoup plus complexe. Notre objectif dans cet exemple est de montrer l’interopérabilité entre .NET et WebSphere avec des types de données complexes. Dans le schéma de cet exemple figurent, en plus des types standards du schéma XML, des types complexes, des types simples, des énumérations, des restrictions, des tableaux et des types qui héritent d’autres types. Les différences entre les deux plates-formes et les problèmes d’interopérabilité que pose le développement de services Web sont plus manifestes dans cet exemple en raison de sa plus grande complexité. Scénario La société Stuff Sellers vend une gamme variée de produits à différents types d’entreprises. Chaque entreprise est autorisée à soumettre des appréciations des produits qu’elle achète. Le scénario étudié dans le service Web 2 est celui d’un service d’évaluation permettant aux utilisateurs de soumettre un rapport pour une évaluation donnée ou d’obtenir une liste de tous les rapports pour une évaluation donnée identifiée par un nombre. Dans cet exemple, nous pouvons considérer que les rapports correspondent aux appréciations et que la somme de ces appréciations permet de générer l’évaluation finale. Schéma de données La figure suivante illustre les éléments majeurs du schéma de données. Figure 16. Principaux éléments du schéma de données du service Web 2. L’élément de plus haut niveau est Ratings (évaluations). Chaque élément Ratings contient un tableau d’éléments ReportSet (jeu de rapports) encapsulé dans le type ReportSetArray (tableau de jeux de rapports). Chaque élément ReportSet contient un tableau d’éléments Report (rapport) encapsulé dans le type ReportArray (tableau de rapports). Ces trois éléments de niveau supérieur sont des types de données qui héritent du type MyBase (ma base). Pour consulter la vraie définition de schéma XML pour ce schéma de données, reportez-vous au fichier WebService2SharedTypes.xsd à télécharger avec les exemples. Comme avec WSDL, il existe deux façons de générer un schéma XML. La première consiste à concevoir manuellement le schéma en utilisant un concepteur de schéma (dans le cas d’un schéma XML, Visual Studio inclut un concepteur). La deuxième consiste à inférer le schéma à partir d’un type .NET existant à l’aide de l’utilitaire xsd.exe. Comme avec WSDL, ces deux techniques peuvent être associées au sein d’un processus itératif permettant de peaufiner le schéma jusqu’à ce qu’il soit parfait. Dans certains cas, le schéma de données a déjà été défini et existe indépendamment de l’implémentation ou de l’interface du service Web. Cette situation peut se présenter si vous devez créer un service Web à partir du modèle de conception d’un système existant. Que vous le conceviez vous-même ou qu’il vous soit fourni, le schéma de données doit être décrit dans un fichier XSD de schéma XML autonome pour que sa modularité et sa réutilisation puissent être garanties. La figure suivante est un diagramme de classes illustrant le schéma XML défini dans le schéma de données pour ce service Web. Ce schéma est défini dans le même code dans le fichier WebService2SharedTypes.xsd. Le code généré à partir du schéma de données (fichier de schéma XML) sur chaque plate-forme doit suivre une structure de classes similaire à celle décrite dans ce diagramme. Figure 17. Diagramme de classes du schéma de données dans le service Web 2. Définition du service Web Dans le premier service Web, nous avons utilisé .NET Framework pour générer le fichier modèle WSDL initial. Comme notre service Web était simple, cette étape suffisait à créer le WSDL. Malheureusement, le présent service Web n’est pas aussi simple et une intervention manuelle s’impose. Pour garantir l’interopérabilité, nous avons dû apporter des modifications au fichier modèle WSDL, entre autres inclure des références à notre schéma de données, changer l’espace de noms et créer les messages appropriés aux portTypes (types de ports). Le document WSDL de ce service Web définit deux opérations : GetRatings et SubmitReport. GetRatings renvoie un type Ratings si l’ID fourni correspond à une évaluation donnée. L’opération SubmitReport permet aux clients de soumettre un nouveau rapport associé à des éléments Ratings et ReportSet spécifiques. Vous pouvez consulter la définition WSDL de ces opérations dans le fichier WebService2.wsdl à télécharger avec les exemples. Il existe deux façons d’inclure les définitions de schéma XML dans des fichiers WSDL : vous pouvez définir le schéma inline dans le fichier WSDL ou référencer vos fichiers de schéma XML dans votre fichier WSDL à l’aide de l’élément xsd:import. Les définitions inline faisant partie du fichier WSDL, leur maintenance est simple. Cependant, comme le schéma de données décrit les données et non l’interface du service Web et qu’il est parfois utilisé indépendamment de cette interface, il est plus logique de séparer ces deux définitions dans deux fichiers distincts. Si votre service Web est simple et n’utilise pas trop de types de données complexes, le schéma inline fonctionnera très bien (dans le cas du service Web 1, par exemple), mais il est généralement recommandé de séparer le schéma de données de la définition du service web. Dans ce service Web, nous avons décidé d’utiliser la méthode xsd:import pour référencer le fichier de schéma XML externe (WebService2SharedTypes.xsd) à partir du fichier WSDL (WebService2.wsdl). Dans le fichier WSDL, cette méthode se présente comme suit : <types> <xs:schema targetNamespace="http://example.org/"> <!-- Import the Shared Types --> <xs:import namespace="http://example.org/sharedtypes" schemaLocation="WebService2SharedTypes.xsd"/> <!-- Import the Message Parameters -> <xs:import namespace="http://example.org/interface" schemaLocation="WebService2Interface.xsd"/> </xs:schema> </types> . . . Implémentation Pour créer l’exemple de code du service Web dans .NET, exécutez wsdl.exe en définissant les paramètres suivants : wsdl.exe WebService2.wsdl WebService2Interface.xsd WebService2SharedTypes.xsd Notez que pour exécuter wsdl.exe, il faut que le fichier WSDL d’entrée et tous les fichiers XSD inclus soient spécifiés ensemble dans la ligne de commande. Lors de l'importation d'un XSD dans un WSDL, il est possible de fournir un attribut schemaLocation. Selon la spécification WSDL, cet attribut sert à indiquer l’emplacement du schéma mais cette indication n’est pas toujours suivie par les outils qui interprètent le fichier WSDL. Dans notre exemple, wsdl.exe n’utilise pas l’indication schemaLocation et il est donc nécessaire de spécifier les fichiers de schéma externes dans la ligne de commande. En revanche, les outils WSDK d’IBM reconnaissent l’indication schemaLocation et chargeront les fichiers directement le moment venu. Soulignons un détail important : l’élément ID de MyBase porte le type xsd:int et inclut l’attribut minOccurs=0. La définition de schéma XML de MyBase se présente comme suit : <xs:complexType name="MyBase"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="1" name="ID" type="xs:int" nillable="true" /> </xs:sequence> </xs:complexType> Si minOccurs=0, la propriété ID peut être exclue du document XML produit. Cela pose un problème sur la plate-forme .NET. Dans .NET, le type xsd:int est mappé vers le type Int32 qui est un type de valeur ; or les types de valeur ne peuvent pas être NULL. Cela signifie principalement qu’il est impossible de déterminer si la propriété ID a été définie puisque toutes les valeurs de Int32 sont valides. La plate-forme .NET Framework résout ce problème en créant une autre variable appelée IDSpecified de type Boolean. Cette variable est vérifiée par la logique de sérialisation XML de .NET qui détermine si la variable ID a été définie, essentiellement en attribuant à la variable ID la valeur NULL/not NULL. Ainsi, lorsque vous tentez d’accéder à la variable ID, vous devez toujours vérifier ou définir d’abord la variable IDSpecified. Pour plus d’informations sur ce modèle d’utilisation, consultez la documentation MSDN sur la classe XmlIgnoreAttribute (en anglais). Une fois traduit en code C#, le type MyBase se présente comme suit : [System.Xml.Serialization.XmlTypeAttribute(Namespace= "http://example.org/sharedtypes")] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Report))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(ReportSet))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Ratings))] public class MyBase { public int ID; [System.Xml.Serialization.XmlIgnoreAttribute()] public bool IDSpecified; } Ce problème ne se pose pas si l’on utilise l’exemple WebSphere. En effet, lorsqu’un xsd:int est utilisé avec minOccurs=0, les outils WSDK génèrent une variable de type java.lang.Integer au lieu de la variable native Java de type int. Le type java.lang.Integer est un type de référence et il est possible pour une variable de ce type de prendre la valeur NULL pour indiquer qu’elle n’a pas été définie. Voici la traduction du type MyBase en code Java obtenue avec les outils WSDK : public class MyBase implements java.io.Serializable { private java.lang.Integer ID; public MyBase() { } public java.lang.Integer getID() { return ID; } public void setID(java.lang.Integer ID) { this.ID = ID; } } La comparaison entre le code C# généré à partir du schéma XML et le code Java généré pour le même schéma met en exergue une autre différence relative à l’inclusion des attributs de code dans le code C#. Comme nous l’avons déjà dit, ces attributs sont utilisés par le sérialiseur XML dans .NET pour faciliter le mappage depuis l’instance de classe vers XML. Java nécessite également des « métadonnées » similaires. Dans le cas du runtime des services Web dans WSDK, ces métadonnées sont stockées de façon indépendante dans un fichier XML qui définit les mappages de types. Pour plus d’informations, consultez la documentation WSDK. Autre point intéressant : si vous examinez les classes générées par les outils .NET ou WSDK, vous constaterez peut-être que les types de données générés sont différents de ceux que vous auriez écrits en tant que développeur, sans tenir compte des questions d’interopérabilité. Regardez la classe Ratings.java générée par WSDK. Si nous enlevons les éléments de gestion interne du code, elle se présente comme suit : public class Ratings extends org.example.MyBase implements java.io.Serializable { private java.lang.String description; private int confidenceLevel; private java.util.Calendar expiration; private org.example.ReportSetArray allReports; public Ratings() { } public java.lang.String getDescription() { return description; } public void setDescription(java.lang.String description) { this.description = description; } public int getConfidenceLevel() { return confidenceLevel; } public void setConfidenceLevel(int confidenceLevel) { this.confidenceLevel = confidenceLevel; } public java.util.Calendar getExpiration() { return expiration; } public void setExpiration(java.util.Calendar expiration) { this.expiration = expiration; } public org.example.ReportSetArray getAllReports() { return allReports; } public void setAllReports(org.example.ReportSetArray allReports) { this.allReports = allReports; } } Il se peut que les membres des données primitives de type int et string correspondent à ce que n’importe quel développeur aurait rédigé : application des conventions JavaBean et empaquetage des méthodes d’obtention et de réglage autour d’un membre privé. Mais c’est alors qu’apparaissent les différences. La valeur de date est gérée par un élément Calendar, et non par un java.util.Date. Et un élément Array est empaqueté par une classe personnalisée, également accessible via une paire de méthodes d’obtention/réglage. Cette classe générée est probablement différente de celle que vous auriez écrite mais elle fonctionne et présente en outre l’avantage d’être interopérable. Vous pourriez faire le même constat concernant le code généré par les outils .NET. Nous avons suivi les grandes étapes du processus décrit dans la section précédente concernant le service Web 1 pour créer deux projets Visual Studio : l’un pour le client et l’autre pour le serveur. De même, nous avons créé deux projets WebSphere avec le WSDK. Tous ces clients et serveurs sont interopérables. Pour voir comment tout cela fonctionne, compilez et exécutez les projets Visual Studio et Eclipse à télécharger avec les exemples. Avant cela, n’oubliez pas de lire le fichier Readme concernant ce téléchargement. Dans cet exemple, nous avons étudié l’utilisation des types de données complexes dans un service Web interopérable. Le schéma XML W3C joue un rôle fondamental dans la définition des types de message et des éléments de données à échanger. Cette section a expliqué comment développer un XSD pour un type de données complexe et comment employer ce schéma XML dans un document WSDL. Elle a également souligné quelques questions adjacentes à connaître, notamment la différence entre les types de valeur et les types de référence dans .NET et les conséquences de cette différence sur la sérialisation XML. Dans l’exemple suivant, nous allons développer davantage les idées abordées dans cet exemple et étudier l’interopérabilité avec les éléments de données extensibles. Service Web 3 : Recherche de produits La plupart des services Web emploient un schéma de données fixe, ce qui signifie que les types de données transmis sur le réseau sont déjà connus au moment de la conception. Mais parfois, les schémas de données statiques ne permettent pas de répondre aux besoins de l’application. Considérons le scénario d’entreprise suivant. Scénario Comme nous l’avons déjà dit dans le service Web 2, Stuff Sellers vend différents produits aussi bien aux consommateurs qu’à d’autres entreprises. Comme cette gamme de produits est très vaste, le directeur nous a demandé de créer un service Web qui faciliterait la recherche de produits dans la base de données. Cet outil sera utilisé indirectement par les consommateurs par le biais de la vitrine Internet du magasin et directement par les autres entreprises. Le directeur souhaiterait que ce service Web satisfasse aux trois exigences suivantes : 1. Il doit accepter les données dynamiques. Dans la mesure où la société Stuff Sellers ajoute souvent de nouveaux types de produits à sa base de données, la définition du service Web doit être suffisamment flexible pour prendre en charge les nouveaux types sans que cela n’interrompe le fonctionnement des clients existants. 2. Il doit prendre en charge les opérations de base. Les entreprises qui recherchent des produits Stuff Sellers seront probablement intéressées par des produits présentant une propriété spécifique. Un magasin de vente au rabais recherchera des produits coûtant moins de 50 centimes, par exemple. Le service Web doit donc être capable de renvoyer des données et permettre au client d’effectuer sans difficulté une simple vérification des résultats obtenus pour cette propriété. 3. Il doit être extensible. La société Stuff Sellers aura probablement besoin de modifier le service Web à l’avenir et souhaite donc que ce service puisse prendre en charge facilement de nouvelles fonctions, avec une incidence minimum sur les clients existants. Outre ces exigences, le directeur souhaiterait que l’interface du service Web soit aussi simple que possible. Plus précisément, le service Web ne devra avoir qu’un seul point d’entrée. Si nous analysons les produits vendus actuellement par Stuff Sellers, nous constatons que tous les produits sont dotés au minimum d’un nom (Name), d’une description (Description), d’un prix (Price) et d’une unité de gestion des stocks (SKU). Ces attributs constituent les propriétés de base de tous nos produits. De plus, chaque produit est doté de son propre jeu d’attributs unique. Par exemple, un film DVD est doté d’un code région (Region), d’un format de vidéo (Format), d’une langue (Language) et d’une date de parution (ReleaseDate). Un livre est doté d’une liste d’auteurs (Authors), d’un éditeur (Publisher), d’un code ISBN (ISBN) et d’une date de publication (PublishedDate). Par conséquent, le type de produit de base exposera toutes les propriétés communes à tous les types de produits et acceptera les extensions pour les propriétés spécifiques à certains types de produits. Schéma de données Voici un diagramme de classes du schéma de données défini pour le service Web 3. Figure 18. Diagramme de classes du schéma de données du service Web 3. La définition de type de schéma XML pour SearchResult (résultat de la recherche) dans WebService3SharedTypes.xsd se présente comme suit : <xs:complexType name="SearchResult"> <xs:sequence> <xs:element minOccurs="1" maxOccurs="1" name="SKU" type="xs:string"/> <xs:element minOccurs="1" maxOccurs="1" name="Price" type="xs:decimal" /> <xs:element minOccurs="1" maxOccurs="1" name="Name" type="xs:string"/> <xs:element minOccurs="1" maxOccurs="1" name="Description" type="xs:string" /> <xs:element minOccurs="1" maxOccurs="1" name="ProductType" type="xs:string" /> <xs:any minOccurs="1" maxOccurs="1" processContents="lax" /> </xs:sequence> </xs:complexType> Comme vous pouvez le constater, SearchResult est le type parent qui représente tous les produits trouvés par le service Web. SearchResult contient les propriétés communes à tous les types de produits. De plus, SearchResult contient également un élément xsd:any qui sert de caractère générique. Un fichier XML conforme à ce schéma peut inclure n’importe quel élément XML à cet emplacement. Dans le cas qui nous intéresse, l’élément xsd:any contiendra l’un des types de propriété de produit que le service Web peut renvoyer. Nous en avons défini trois dans le fichier WebService3ExtendedProperties.xsd : DvdProperties, BookProperties et CDProperties (respectivement, propriétés du DVD, du livre et du CD). Dans la pratique, l’application cliente accèdera aux propriétés communes en vérifiant SearchResult et accèdera aux propriétés étendues de ce produit en vérifiant la variable du membre contenant les types de propriétés spécifiques du produit. Plutôt que d’utiliser xsd:any dans le schéma pour le mappage des éléments XML, il est également possible d’employer un élément string (chaîne) dans le schema qui contiendra le XML généré de façon dynamique. L’utilisation d’une chaîne est similaire à celle du caractère générique. La différence, c’est que le XML contenu dans la chaîne est défini pour échapper à la transmission et sera donc opaque pour les analyseurs XML, ce qui n’est pas ce que nous souhaitons. Il est plus sain d’intégrer le XML dynamique dans le document XML que de définir son échappement dans un élément de chaîne. Dans les deux cas, du travail supplémentaire s’impose, qu’il s’agisse de générer le XML côté expédition ou de l’analyser côté réception. Définition du service Web Comme dans le deuxième exemple, le document WSDL de ce service Web est composé de deux fichiers : WebService3.wsdl et WebService3SharedTypes.xsd. WebService3.wsdl contient les déclarations définissant le service Web et WebService3SharedTypes.xsd est un fichier de schéma XML contenant les types de données utilisés par le service Web. Voici un exemple de capture de message SOAP renvoyé par le service Web au client. <?xml version="1.0" encoding="utf-8" ?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Body> <SearchResponse xmlns="http://example.org/sharedtypes"> <SearchResult> <SearchResult> <SKU>B05502HB9I</SKU> <Price>14.99</Price> <Name>Spain's History</Name> <Description>Short documentary on the history of the Spain.</Description> <ProductType>DVD</ProductType> <DvdProperties xmlns="http://example.org/resulttypes"> <Region>EUROPE</Region> <Format>PAL</Format> <Language>Spanish</Language> <ReleaseDate>2000-0514</ReleaseDate> </DvdProperties> </SearchResult> <SearchResult> <SKU>A04D5E87RJ</SKU> <Price>20.00</Price> <Name>Spain's History</Name> <Description>Companion coffee table book to the documentary "Spain's History".</Description> <ProductType>Book</ProductType> <BookProperties xmlns="http://example.org/resulttypes"> <Authors> <Author>Mark Person</Author> </Authors> <Publisher>BookPub Central</Publisher> <ISBN>0459756212</ISBN> <PublishedDate>2003-08-08</PublishedDate> </BookProperties> </SearchResult> </SearchResult> </SearchResponse> </soap:Body> </soap:Envelope> Il existe également un troisième fichier, WebService3ExtendedProperties.xsd, qui n’est pas importé dans le WSDL mais qui est essentiel pour que le service Web puisse générer une réponse et que les clients puissent interpréter cette réponse. Ce fichier contient les définitions de la partie dynamique des données : les propriétés étendues des types de produits. La séparation des types de produits des types utilisés par le service Web présente un avantage : la possibilité d’étendre les types de produits sans modifier l’interface. À l’avenir, Stuff Sellers sera amené à étendre son activité et à vendre d’autres types de produits. Comme les résultats de recherche contiendront ces nouveaux produits, il est indispensable que l’ajout de nouveaux produits puisse s’effectuer sans difficulté. Dans notre modèle de conception, la prise en charge de nouveaux types est simple : il suffit d’étendre l’implémentation du service Web pour qu’il renvoie ces nouveaux types et d’ étendre le schéma de données défini dans WebService3ExtendedProperties.xsd. Dernières étapes : publier le nouveau fichier XSD sur le Web et informer les utilisateurs de cette modification. Il n’est pas nécessaire de modifier le fichier WSDL. Les clients du service Web qui ne souhaitent pas utiliser les propriétés étendues ou se contentent de les transmettre aux autres services peuvent choisir d’ignorer ces propriétés. À l’exécution, ces clients n’ont pas besoin de désérialiser le Blob XML dans des objets. Par exemple, si une application est écrite pour filtrer des produits uniquement en fonction du prix, le type de produit renvoyé n’a aucune importance. Dans ce cas, le client doit vérifier uniquement la propriété de base Price du type SearchResult et peut ignorer en toute sécurité les propriétés étendues. Grâce à la flexibilité qu’offre l’élément xsd:any, le service Web peut ajouter de nouvelles fonctions sans que cela n’interrompe le fonctionnement des clients existants. Les nouveaux clients du service Web pourront utiliser les nouveaux types de produits tandis que les applications existantes se contenteront de les ignorer. Même si des types de produits sont éliminés, les clients existants du service Web continueront de fonctionner correctement : il leur suffira de ne pas exécuter le code appartenant aux anciens produits. Cette méthode de conception offre un compromis idéal : les messages de réponse peuvent être étendus pour les nouvelles applications sans que cela n’altère le bon fonctionnement des applications existantes. Implémentation On appelle « sérialisation XML » ou « liaison XML » la conversion entre le XML et les instances correspondantes des objets. En général, la conversion de paramètres en appel de service Web et du XML transmis en demande ou en réponse du service Web est effectuée automatiquement par le runtime des services Web. Cependant, si l’on utilise des extensions de schéma non définies dans le WSDL (ou les XSD correspondants importés), cette sérialisation et cette désérialisation XML doivent être effectuées manuellement. La plate-forme .NET propose des outils et des interfaces de programmation permettant cela. IBM WebSphere ne propose pas d’interface publique permettant d’effectuer manuellement une sérialisation XML. Le client et le serveur Java doivent être dotés d’une capacité complémentaire pour effectuer la liaison entre Java et XML. Il s’agit de l’API JAXB qui fait partie du kit des développeurs de services Web Java de de Sun (JWSDP). Le kit JWSDP vous fournira le compilateur JAXB, capable de générer la classe Java à partir du schéma XML de façon similaire à l’utilitaire xsd.exe dans .NET. Il est beaucoup plus simple d’utiliser les classes pour référencer les types de données que de manipuler directement les éléments XML. Dans JAXB, les classes de type de données générées sont également responsables des opérations d’ordonnancement (ou « marshalling ») et de « sérialisation » vers les fichiers XML (qui doivent être conformes au XSD), ainsi que des opérations inverses. Le SDK de .NET Framework inclut également des outils et une infrastructure de liaison des données XML avec les classes .NET. L’outil Xsd.exe analyse les fichiers XSD pour créer un fichier de code source correspondant contenant les classes de définition des types de données. À l’exécution, les applications peuvent utiliser l’espace de noms System.Xml.Serialization des classes pour créer un graphique d’objet à partir du XML transmis et inversement. Par exemple, à la compilation, pour générer des classes C# à partir du schéma WebService3ExtendedProperties.xsd, utilisez la commande suivante : xsd.exe /classes WebService3ExtendedProperties.xsd Ensuite, à l’exécution, pour créer le graphique d’objet à partir d’un fichier, utilisez ces quelques lignes de code : FileStream fs = new FileStream(path, FileMode.Open); XmlSerializer s= new XmlSerializer(typeof(BookPropertiesType)); BookPropertiesType props; try { props= (BookPropertiesType) s.Deserialize(fs); } finally { fs.Close(); } Normalement, pour les types de données utilisés dans les interfaces des services Web, la création des classes de type de données et la sérialisation sont exécutées automatiquement : la création des classes est exécutée par l’outil wsdl.exe ou par l’utilitaire « Ajouter une référence Web » dans Visual Studio et la sérialisation, par le runtime des services Web dans .NET. Lorsque vous transmettez un paramètre à un service Web, ce paramètre est automatiquement sérialisé en XML pour pouvoir être transmis sur le réseau. Il est nécessaire ici d’utiliser xsd.exe pour créer les classes car le schéma WebService3ExtendedProperties.xsd n’est pas explicitement inclus dans la définition de l’interface du service Web. Qu’en est-il de la sérialisation à l’exécution ? Lorsque l’outil xsd.exe analyse un schéma, il mappe les éléments xsd:any vers des champs de type XmlElement. En modifiant les classes générées, en changeant le type de champ par System.Object plutôt que par System.Xml.XmlElement et en associant au champ les attributs XmlElementAttribute, nous pouvons demander à l’infrastructure de mapper les données XML vers des types de données .NET spécifiques plutôt que vers un élément générique XmlElement. Par exemple, dans cet extrait de code, le champ Any sera mappé vers l’une des trois propriétés étendues. [System.Xml.Serialization.XmlElementAttribute(ElementName="DvdProperties", Type=typeof(NetWSServer3.DvdPropertiesType), Namespace="http://example.org/resulttypes")] [System.Xml.Serialization.XmlElementAttribute(ElementName="BookProperties", Type=typeof(NetWSServer3.BookPropertiesType), Namespace="http://example.org/resulttypes")] [System.Xml.Serialization.XmlElementAttribute(ElementName="CDProperties", Type=typeof(NetWSServer3.CDPropertiesType), Namespace="http://example.org/resulttypes")] public object Any; Ces modifications des classes générées permettront au runtime de .NET de sérialiser de façon automatique et implicite les types d’objet de propriété Product depuis et vers XML. Sans cette fonctionnalité, le développeur devrait effectuer une sérialisation explicite de ces classes vers XML. C’est d’ailleurs la solution retenue par WebSphere pour les types de propriété étendus. (Pour plus de détails, voir l’exemple de code). Lors du développement de cet exemple, il a fallu déterminer entre autres s’il fallait faire de ProductType une énumération ou une simple chaîne. L’avantage avec l’énumération, c’est que les différents types sont indiqués de façon explicite et qu’il n’y a donc pas de confusion possible. Cependant, nous avons finalement décidé de ne pas utiliser d’énumération à cause des exigences d’extensibilité : notre solution doit être suffisamment flexible pour qu’il soit possible de créer des types de produits supplémentaires ou d’en éliminer sans que cela n’interrompe le fonctionnement des clients existants. Si ProductType est défini en tant qu’énumération, la transmission de nouveaux types de produits à d’anciens clients entraînerait un dysfonctionnement. C’est pourquoi nous avons utilisé une chaîne, celle-ci offrant la possibilité d’étendre les lignes de produits sans interrompre le fonctionnement des clients du service Web existants. Comme dans le service Web 2, les types de données générés ici par les outils seront probablement différents de ceux qu’aurait rédigés un développeur tentant de modéliser le problème sous forme de code. Cependant, là encore, le principal avantage de commencer par le WSDL et de générer le code réside dans l’interopérabilité. Comme dans le service Web 2, nous avons suivi les grandes étapes du processus présenté dans l’introduction de cet article pour produire des projets de clients et de serveurs pour Visual Studio et le kit de développement (SDK) de services Web WebSphere. Une fois encore, les clients et serveurs obtenus offrent une interopérabilité totale. Bien, arrêtons les discussions et passez à l'action. Compilez et exécutez les projets Visual Studio et Eclipse dans les exemples à télécharger pour vérifier l’extensibilité de ce service Web 3 en action. Le service Web 3 illustre l’utilisation de types de données complexes inclus de façon statique ou dynamique dans un document WSDL. La prise en charge des types extensibles permet d’étendre et de modifier l’interface en réduisant au minimum les modifications à apporter au service Web et aux clients de ce service. Conclusion Les trois services Web décrits dans cet article démontrent qu’il est tout à fait possible de créer des services Web interopérables en utilisant des types de données complexes. La méthode la plus simple avec les outils de développement, qui commence par l’implémentation (« Implementation First »), pose souvent des problèmes d’interopérabilité. En revanche, si l’on définit d’abord l’interface du service Web (WSDL) et que l’on génère ensuite les clients et les serveurs à partir de cette définition d’interface, il est possible d’éviter de nombreux pièges pouvant compromettre l’interopérabilité. Bien que nous ayons limité notre approche « WSDL First » à .NET et à WebSphere, les concepts illustrés s’appliquent aux problèmes d’interopérabilité entre n’importe quelles plates-formes. La rédaction manuellement du WSDL n’est pas simple. Cet article a aussi décrit la méthode consistant à développer sur le mode itératif et à peaufiner les fichiers WSDL et les définitions de schéma XML W3C en utilisant les outils de prototype inclus dans Visual Studio .NET et WSDK. Enfin, cet article fournit des conseils et des indications sur les pièges possibles que recèle la création de services Web interopérables et extensibles. Si nous poursuivons cet idéal d’interopérabilité des services Web, peut-être le rêve d’un accès omniprésent à n’importe quel système, indépendamment de la plate-forme ou de l’architecture, deviendra-t-il réalité. Voici la liste des recommandations et astuces mentionnées dans cet article. Recommandations/Astuces Utilisation des documents WSDL 1. Lors de la rédaction manuelle du code WSDL, prenez garde au contexte de l’espace de noms actuel. La chaîne de l’espace de noms doit être rigoureusement identique dans toutes les références au même espace de noms. 2. Si votre service Web doit retourner un type de données complexe pour lequel le mappage vers et depuis XML n’est pas bien défini, envisagez de créer votre propre type de données pour représenter les mêmes données. Cela optimise la portabilité sans empêcher la transmission des données nécessaires. 3. Pour garantir la modularité et la réutilisation, utilisez l’importation XSD plutôt que les définitions de schéma inline. Reportez-vous aux exemples du service Web 2 et du service Web 3. 4. Prenez garde aux attributs optionnels des types de données dans la définition de schéma XML. Certains d’entre eux peuvent altérer le mode de génération du code d’implémentation. Reportez-vous à l’exemple du service Web 2 sur la définition de la variable ID. 5. WS-I (Web Services Interoperability Organization), organisation pour l’interopérabilité des services Web, est un organisme officiel créé pour promouvoir l’interopérabilité des services Web entre toutes les plates-formes et langues. Cette organisation a créé une spécification pour l’interopérabilité appelée WS-I Basic Profile et fournit des outils permettant de vérifier si les fichiers WSDL respectent cette spécification. 6. Lorsqu’une exception se produit dans le service Web, un élément SOAPFault est renvoyé dans le message SOAP. En principe, l’infrastructure du service Web s’empare de l’élément SOAPFault et renvoie sa propre classe d’exception. Dans .NET Framework, SOAPFault est pris et empaqueté dans la classe System.Web.Services.Protocols.SoapException. Dans WebSphere, l’exception renvoyée est com.ibm.ws.webservices.engine.WebServicesFault. Vous trouverez des informations complémentaires sur les erreurs dans les propriétés des classes d’exceptions mentionnées. 7. Tous les types complexes XSD sont convertis en classes. Comme Java ne prend pas actuellement en charge les énumérations, les valeurs d’énumérations sont traduites en constantes public static string dans la classe Java correspondante. 8. Certaines fonctionnalités du schéma XSD ne sont pas bien représentées sur certaines plates-formes actuelles, par exemple les types d’entiers non signés (unsigned integer) dans XSD. Si .NET fournit des types non signés, ce n’est pas le cas de Java. C’est pourquoi il n’est pas recommandé de les utiliser dans des services Web interopérables. 9. L’utilisation des tableaux est risquée si l’on utilise des conditions de limitation. Dans .NET, si un tableau vide est sérialisé, seul l’élément d’en-tête XML est créé. Si un tableau NULL est sérialisé, il est totalement ignoré et aucune sérialisation en XML ne sera effectuée. En revanche, Java sérialise l’élément XML dans les deux cas. La différence, c’est que pour la case NULL, le runtime des services Web dans WebSphere balise l’élément XML à l’aide d’un attribut xsd:nil=true pour indiquer que le tableau porte la valeur Null. 10. Lorsqu’un client WebSphere désérialise un tableau .NET vide, il crée un tableau avec une référence NULL. Si un client WebSphere désérialise un tableau .NET Null, une exception java.lang.NullPointerException est insérée. 11. Lorsqu’un client .NET désérialise un tableau WebSphere vide, il crée un tableau d’une longueur égale à zéro. Lorsqu’un client .NET désérialise un tableau WebSphere Null, il attribue la valeur NULL à la référence du tableau. Utilisation de Wsdl.exe Lorsqu’un fichier WSDL référence d’autres fichiers (par exemple, des fichiers XSD), vous devez fournir de façon explicite l’emplacement de ces fichiers sous forme d’arguments dans wsdl.exe. Pour des raisons de sécurité, wsdl.exe ne charge pas automatiquement les fichiers indiqués par des références schemaLocation dans le fichier WSDL. Par exemple, si vous générez le code pour le service Web 2, vous devrez exécuter la commande suivante : wsdl.exe WebService2.wsdl WebService2Interface.xsd WebService2SharedTypes.xsd Reportez-vous au service Web 2 pour plus d’informations sur ce point. Wsdl.exe génère par défaut un exemple de code en C#. Si vous souhaitez utiliser un autre langage (ex. : VB.NET), insérez l’option /l dans la commande : wsdl.exe /l:vb WebService1.wsdl Lorsque l’outil génère un fichier source pour le service Web (à l’aide de l’option /server), il crée un modèle de classe abstraite associé au document WSDL. Il est recommandé de modifier ce fichier directement plutôt que de sous-classer le fichier généré. Pour plus d’informations à ce sujet, reportez-vous au service Web 2. Visual Studio .NET 2005 prendra en charge la génération de fichiers source modèles directement à partir du WSDL dans l’IDE. De plus, si le document WSDL est modifié et qu’il faut générer à nouveau le fichier modèle, c’est l’IDE qui s’en chargera en s’assurant que tout le code existant dans l’ancien modèle a bien été reporté dans le nouveau modèle. Utilisation de Visual Studio .NET Lors de la création d’une application de service Web dans Visual Studio .NET, le projet généré fournit automatiquement une page de test qui s’affiche à l’exécution du service Web à partir du navigateur. Comme nous développons des services Web en utilisant la méthode qui consiste à créer d’abord le fichier WSDL (« WSDL First »), nous vous recommandons de désactiver ce WSDL généré automatiquement et de publier votre propre WSDL dans un emplacement public. Pour désactiver la page de test et le WSDL générés automatiquement, ajoutez les éléments suivants dans l’élément <system.web> du fichier web.config de votre projet : <webServices> <protocols> <remove name="Documentation" /> </protocols> </webServices> Outre la technique consistant à utiliser wsdl.exe pour créer une classe proxy de service Web, vous pouvez aussi opter pour une méthode plus conviviale grâce aux outils intégrés dans Visual Studio.NET. La boîte de dialogue Ajouter une référence Web vous demandera d’effectuer le pointage vers un fichier WSDL, qui sera utilisé pour générer une classe proxy. L’opération effectuée en coulisses par la boîte de dialogue consiste essentiellement à appeler wsdl.exe à l’arrière plan pour traiter le fichier WSDL. Malheureusement, si vous tentez de créer un proxy client en utilisant la commande Ajouter une référence Web de Visual Studio .NET 2002 pour effectuer un pointage vers un document WSDL utilisant xsd:import, vous obtiendrez un bogue. Si c’est le cas, utilisez toujours la commande wsdl.exe pour générer le proxy client. Ce bogue a été corrigé dans Visual Studio .NET 2003, désormais capable de récupérer tous les fichiers importés et de générer ensuite la classe de proxy client. Création du WSDL avec Eclipse Grâce aux outils fournis dans le WSDK, Eclipse offre une fonctionnalité des services Web similaire à celle de Visual Studio. Dans Eclipse, vous devez créer un fichier Java Bean ou EJB qui servira de modèle pour le service Web. Vous pouvez également recourir à des utilitaires de ligne de commande pour générer les fichiers modèles du service Web. Pour plus d’informations sur cette procédure, consultez la documentation d’Eclipse. Remerciements Nous remercions Simon Guest de Microsoft pour ses comptes-rendus et commentaires techniques excellents, ainsi que Neil Chopra et Mike Hanley de Vertigo Software pour avoir testé et enrichi certaines idées développées dans cet article. Liens utiles Titre Emplacement Page d’accueil des http://msdn.microsoft.com/webservices/ (en anglais) services Web de Microsoft Page d’accueil des services Web d’IBM http://www.ibm.com/webservices (en anglais) Page d’accueil des services Web de BEA http://www.bea.com/webservices/ (en anglais) Organisation WS-I http://www.ws-i.org Version finale de la spécification WS-I Basic Profile http://www.ws-i.org/Profiles/BasicProfile-1.0.html (en anglais) Yasser Shohoud http://msdn.microsoft.com/msdnmag/issues/02/12/WebServicesDesign/ (en anglais) Will Provost http://webservices.xml.com/pub/a/ws/2003/07/22/wsdlfirst.html (en anglais)