Concevoir un cadre d'application J2EE simple orienté services

Aujourd'hui, les développeurs sont inondés de frameworks open source qui aident à la programmation J2EE: Struts, Spring, Hibernate, Tiles, Avalon, WebWorks, Tapestry ou Oracle ADF, pour n'en nommer que quelques-uns. De nombreux développeurs trouvent que ces frameworks ne sont pas la panacée à leurs problèmes. Ce n'est pas parce qu'ils sont open source qu'ils sont faciles à changer et à améliorer. Lorsqu'un framework est insuffisant dans un domaine clé, ne concerne qu'un domaine spécifique, ou est simplement gonflé et trop cher, vous devrez peut-être créer votre propre framework par-dessus. Construire un framework comme Struts est une tâche non triviale. Mais il n'est pas nécessaire de développer progressivement un cadre qui exploite Struts et d'autres cadres.

Dans cet article, je vous montre comment développer X18p (Xiangnong 18 Palm, du nom d'un puissant combattant de kung fu légendaire), un exemple de cadre qui résout deux problèmes courants ignorés par la plupart des frameworks J2EE: le couplage serré et le DAO gonflé (objet d'accès aux données) code. Comme vous le verrez plus tard, X18p exploite Struts, Spring, Axis, Hibernate et d'autres frameworks à différentes couches. J'espère qu'avec des étapes similaires, vous pourrez facilement déployer votre propre framework et le faire évoluer d'un projet à l'autre.

L'approche que j'adopte pour développer ce cadre utilise des concepts issus du Rational Unified Process (RUP) d'IBM. Je suis ces étapes:

  1. Fixez-vous des objectifs simples au départ
  2. Analyser l'architecture d'application J2EE existante et identifier les problèmes
  3. Comparez les frameworks alternatifs et sélectionnez celui qui est le plus simple à construire
  4. Développer le code de manière incrémentielle et refactoriser souvent
  5. Rencontrez l'utilisateur final du framework et recueillez régulièrement des commentaires
  6. Tester, tester, tester

Étape 1. Fixez-vous des objectifs simples

Il est tentant de se fixer des objectifs ambitieux et de mettre en œuvre un cadre de pointe qui résout tous les problèmes. Si vous disposez de ressources suffisantes, ce n'est pas une mauvaise idée. En règle générale, l'élaboration d'un cadre initial pour votre projet est considérée comme une surcharge qui ne fournit pas de valeur commerciale tangible. Commencer plus petit vous aide à réduire les risques imprévus, à profiter de moins de temps de développement, à réduire la courbe d'apprentissage et à obtenir l'adhésion des parties prenantes du projet. Pour X18p, je ne me suis fixé que deux objectifs en fonction de mes précédentes rencontres avec le code J2EE:

  1. Réduire le Actioncouplage de code J2EE
  2. Réduit la répétition du code au niveau de la couche J2EE DAO

Dans l'ensemble, je souhaite fournir un code de meilleure qualité et réduire le coût total de développement et de maintenance en augmentant ma productivité. Avec cela, nous passons par deux itérations des étapes 2 à 6 pour atteindre ces objectifs.

Réduire le couplage de code

Étape 2. Analyser l'architecture d'application J2EE précédente

Si un framework d'application J2EE est en place, nous devons d'abord voir comment il peut être amélioré. De toute évidence, partir de zéro n'a pas de sens. Pour X18p, regardons un exemple d'application J2EE Struts typique, illustré à la figure 1.

Actionappels XXXManageret XXXManagerappels XXXDAOs. Dans une conception J2EE typique qui incorpore des Struts, nous avons les éléments suivants:

  • HttpServletou un Actioncalque Struts qui gère HttpRequestetHttpResponse
  • Couche de logique métier
  • Couche d'accès aux données
  • Couche de domaine qui correspond aux entités du domaine

Quel est le problème avec l'architecture ci-dessus? La réponse: un couplage serré. L'architecture fonctionne très bien si la logique Actionest simple. Mais que faire si vous avez besoin d'accéder à de nombreux composants EJB (Enterprise JavaBeans)? Que faire si vous devez accéder aux services Web à partir de diverses sources? Et si vous avez besoin d'accéder à JMX (Java Management Extensions)? Struts dispose-t-il d'un outil qui vous aide à rechercher ces ressources à partir du struts-config.xmlfichier? La réponse est non. Struts est censé être une infrastructure de niveau Web uniquement. Il est possible de coder les Actions comme différents clients et d'appeler le back-end via le modèle Service Locator. Cependant, cela mélangera deux types de code différents dans Actionla execute()méthode de.

Le premier type de code concerne le niveau Web HttpRequest/ HttpResponse. Par exemple, le code récupère les données de formulaire HTTP à partir de ActionFormou HttpRequest. Vous disposez également d'un code qui définit les données dans une requête HTTP ou une session HTTP et les transmet à une page JSP (JavaServer Pages) à afficher.

Le deuxième type de code, cependant, concerne le niveau métier. Dans Action, vous appelez également du code backend tel qu'une EJBObjectrubrique JMS (Java Message Service), ou même des sources de données JDBC (Java Database Connectivity) et récupérez les données de résultat à partir des sources de données JDBC. Vous pouvez utiliser le modèle Service Locator Actionpour vous aider à effectuer la recherche. Il est également possible Actionde référencer uniquement un POJO local (ancien objet Java simple) xxxManager. Néanmoins, un objet backend ou xxxManagerles signatures au niveau de la méthode sont exposés Action.

C'est comme ça que ça Actionmarche, non? La nature de Actionest un servlet qui est censé se soucier de la façon de récupérer des données à partir de HTML et de définir des données vers HTML avec une requête / session HTTP. Il s'interface également avec la couche de logique métier pour obtenir ou mettre à jour des données à partir de cette couche, mais sous quelle forme ou quel protocole, cela Actionne s'en soucie pas.

Comme vous pouvez l'imaginer, lorsqu'une application Struts se développe, vous pourriez vous retrouver avec des références étroites entre les Actions (niveau Web) et les chefs d'entreprise (niveau métier) (voir les lignes rouges et les flèches dans la figure 1).

Pour résoudre ce problème, nous pouvons considérer les cadres ouverts sur le marché - laissez-les inspirer notre propre réflexion avant d'avoir un impact. Spring Framework vient sur mon écran radar.

Étape 3. Comparez les autres frameworks

Le cœur de Spring Framework est un concept appelé BeanFactory, qui est une bonne implémentation d'usine de recherche. Il diffère du modèle de localisateur de service en ce qu'il possède une fonction d'inversion de contrôle (IoC) précédemment appelée dépendance d'injection . L'idée est d'obtenir un objet en appelant votre ApplicationContextde » getBean()méthode. Cette méthode recherche le fichier de configuration Spring pour les définitions d'objet, crée l'objet et renvoie un java.lang.Objectobjet. getBean()est bon pour les recherches d'objets. Il semble qu'une seule référence d'objet,, ApplicationContextdoit être référencée dans le Action. Cependant, ce n'est pas le cas si nous l'utilisons directement dans le Action, car nous devons convertir getBean()le type d'objet de retour de EJB / JMX / JMS / Web.Actiondoit toujours être conscient de l'objet backend au niveau de la méthode. Le couplage serré existe toujours.

Si nous voulons éviter une référence au niveau de la méthode objet, que pouvons-nous utiliser d'autre? Naturellement, le service vient à l'esprit. Le service est un concept omniprésent mais neutre. Tout peut être un service, pas nécessairement uniquement les soi-disant services Web. Actionpeut également traiter la méthode d'un bean session sans état comme un service. Il peut également considérer l'appel d'un sujet JMS comme consommant un service. La façon dont nous concevons pour consommer un service peut être très générique.

Avec la stratégie formulée, le danger repéré et le risque atténué grâce à l'analyse et à la comparaison ci-dessus, nous pouvons stimuler notre créativité et ajouter une fine couche de courtier de services pour démontrer le concept orienté service.

Étape 4. Développer et refactoriser

Pour implémenter la réflexion conceptuelle orientée services dans le code, nous devons prendre en compte les éléments suivants:

  • La couche Service Broker sera ajoutée entre le niveau Web et le niveau métier.
  • Conceptuellement, an Actionappelle uniquement une demande de service métier, qui transmet la demande à un routeur de service. Le routeur de service sait comment connecter les demandes de service métier à différents contrôleurs ou adaptateurs de fournisseur de services en recherchant un fichier XML de mappage de service X18p-config.xml.
  • Le contrôleur du fournisseur de services a une connaissance spécifique de la recherche et de l'appel des services commerciaux sous-jacents. Ici, les services commerciaux peuvent être n'importe quoi, des services POJO, LDAP (protocole d'accès à l'annuaire léger), EJB, JMX, COM et Web aux API de produits COTS (commerciaux prêts à l'emploi). X18p-config.xmldevrait fournir suffisamment de données pour aider le contrôleur du fournisseur de services à faire le travail.
  • Tirez parti de Spring pour la recherche et les références d'objets internes de X18p.
  • Créez des contrôleurs de fournisseur de services de manière incrémentielle. Comme vous le verrez, plus il y a de contrôleurs de fournisseur de services implémentés, plus le X18p a de puissance d'intégration.
  • Protégez les connaissances existantes telles que Struts, mais gardez les yeux ouverts pour de nouvelles choses à venir.

Maintenant, nous comparons le Actioncode avant et après l'application du framework X18p orienté services:

Struts Action sans X18p

public ActionForward execute (mappage ActionMapping, formulaire ActionForm, requête HttpServletRequest, réponse HttpServletResponse) lève IOException, ServletException {... UserManager userManager = new UserManager (); Chaîne userIDRetured = userManager.addUser ("John Smith") ...}

Struts Action avec X18p

public ActionForward execute (mappage ActionMapping, formulaire ActionForm, requête HttpServletRequest, réponse HttpServletResponse) jette IOException, ServletException {... ServiceRequest bsr = this.getApplicationContext (). getBean ("businessServiceRequest"); bsr.setServiceName ("Services aux utilisateurs"); bsr.setOperation ("addUser"); bsr.addRequestInput ("param1", "addUser"); String userIDRetured = (String) bsr.service (); ...}

Spring prend en charge les recherches dans la demande de service métier et d'autres objets, y compris les gestionnaires POJO, le cas échéant.

La figure 2 montre comment le fichier de configuration Spring,, applicationContext.xmlprend en charge la recherche de businessServiceRequestet serviceRouter.

Dans ServiceRequest.java, la service()méthode appelle simplement Spring pour trouver le routeur de service et se transmet au routeur:

public Object service () {return ((ServiceRouter) this.serviceContext.getBean ("service router")). route (this); }

Le routeur de service dans X18p achemine les services utilisateur vers la couche de logique métier avec X18p-config.xmll'aide de. Le point clé est que le Actioncode n'a pas besoin de savoir où et comment les services utilisateur sont mis en œuvre. Il doit uniquement connaître les règles de consommation du service, telles que le transfert des paramètres dans le bon ordre et la conversion du bon type de retour.

La figure 3 montre le segment X18p-config.xmlqui fournit les informations de mappage de service, qui ServiceRouterseront recherchées dans X18p.

Pour les services utilisateur, le type de service est POJO. ServiceRoutercrée un contrôleur de fournisseur de services POJO pour gérer la demande de service. Ce POJO springObjectIdest userServiceManager. Le contrôleur du fournisseur de services POJO utilise Spring pour rechercher ce POJO avec springObjectId. Comme userServiceManagerpointe vers le type de classe X18p.framework.UserPOJOManager, la UserPOJOManagerclasse est le code logique spécifique à l'application.

Examiner ServiceRouter.java:

La route d'objet public (ServiceRequest serviceRequest) lève l'exception {// / 1. Lisez tout le mappage à partir du fichier XML ou récupérez-le depuis Factory // Config config = xxxx; // 2. Récupère le type de service depuis config. String businessServiceType = Config.getBusinessServiceType (serviceRequest.getServiceName ()); // 3. Sélectionnez le routeur / gestionnaire / contrôleur correspondant pour le traiter. if (businessServiceType.equalsIgnoreCase ("LOCAL-POJO")) {POJOController pojoController = (POJOController) Config.getBean ("POJOController"); pojoController.process (serviceRequest); } else if (businessServiceType.equalsIgnoreCase ("WebServices")) {String endpoint = Config.getWebServiceEndpoint (serviceRequest.getServiceName ()); WebServicesController ws = (WebServicesController) Config.getBean ("WebServicesController"); ws.setEndpointUrl (point final); ws.process (serviceRequest); } else if (businessServiceType.equalsIgnoreCase ("EJB")) {EJBController ejbController = (EJBController) Config.getBean ("EJBController"); ejbController.process (serviceRequest); } else {// TODO System.out.println ("Types inconnus, c'est à vous comment le gérer dans le framework"); } // Voilà, c'est votre framework, vous pouvez ajouter n'importe quel nouveau ServiceProvider pour votre prochain projet. return null; }c'est votre framework, vous pouvez ajouter n'importe quel nouveau ServiceProvider pour votre prochain projet. return null; }c'est votre framework, vous pouvez ajouter n'importe quel nouveau ServiceProvider pour votre prochain projet. return null; }

Le bloc if-else de routage ci-dessus pourrait être refactorisé en un modèle de commande. L' Configobjet fournit la recherche de configuration XML Spring et X18p. Tant que des données valides peuvent être récupérées, c'est à vous de décider comment implémenter le mécanisme de recherche.

En supposant qu'un gestionnaire POJO,, TestPOJOBusinessManagerest implémenté, le contrôleur du fournisseur de services POJO ( POJOServiceController.java) recherche alors la addUser()méthode dans le TestPOJOBusinessManageret l'invoque avec réflexion (voir le code disponible dans Resources).

En introduisant trois classes ( BusinessServiceRequester, ServiceRouteret ServiceProviderController) plus un fichier de configuration XML, nous avons un cadre axé sur le service comme une preuve de concept. Ici Actionn'a aucune connaissance sur la façon dont un service est mis en œuvre. Il ne se soucie que de l'entrée et de la sortie.

La complexité de l'utilisation de diverses API et modèles de programmation pour intégrer divers fournisseurs de services est protégée des développeurs Struts travaillant sur le niveau Web. Si X18p-config.xmlest conçu à l'avance comme un contrat de service, Struts et les développeurs backend peuvent travailler simultanément par contrat.

La figure 4 montre le nouveau look de l'architecture.

J'ai résumé les contrôleurs des fournisseurs de services courants et les stratégies de mise en œuvre dans le tableau 1. Vous pouvez facilement en ajouter d'autres.

Tableau 1. Stratégies de mise en œuvre pour les contrôleurs des fournisseurs de services communs