Programmering

Design en enkel serviceorienteret J2EE-applikationsramme

I dag er udviklere oversvømmet med open source-rammer, der hjælper med J2EE-programmering: Struts, Spring, Hibernate, Tiles, Avalon, WebWorks, Tapestry eller Oracle ADF, for at nævne nogle få. Mange udviklere finder ud af, at disse rammer ikke er universalmiddel for deres problemer. Bare fordi de er open source betyder det ikke, at de er lette at ændre og forbedre. Når en ramme mangler i et nøgleområde, kun adresserer et bestemt domæne eller bare er oppustet og for dyrt, skal du muligvis bygge din egen ramme oven på den. At bygge en ramme som Struts er en ikke-triviel opgave. Men gradvis at udvikle en ramme, der udnytter stivere og andre rammer, behøver ikke at være.

I denne artikel viser jeg dig, hvordan du udvikler dig X18p (Xiangnong 18 Palm, opkaldt efter en legendarisk magtfuld kung fu-fighter), en prøveramme, der løser to almindelige problemer ignoreret af de fleste J2EE-rammer: tæt kobling og oppustet DAO (dataadgangsobjekt) kode. Som du vil se senere, udnytter X18p stivere, fjeder, akse, dvale og andre rammer i forskellige lag. Forhåbentlig kan du med lignende trin nemt rulle dine egne rammer og dyrke det fra projekt til projekt.

Den tilgang, jeg tager i udviklingen af ​​denne ramme, bruger koncepter fra IBMs Rational Unified Process (RUP). Jeg følger disse trin:

  1. Sæt oprindelige enkle mål
  2. Analyser den eksisterende J2EE-applikationsarkitektur og identificer problemerne
  3. Sammenlign alternative rammer, og vælg den, der er enklest at bygge med
  4. Udvikl kode trinvist og refaktor ofte
  5. Mød med frameworkets slutbruger og indsaml feedback regelmæssigt
  6. Test, test, test

Trin 1. Sæt enkle mål

Det er fristende at sætte ambitiøse mål og implementere en banebrydende ramme, der løser alle problemer. Hvis du har tilstrækkelige ressourcer, er det ikke en dårlig idé. Generelt betragtes udviklingen af ​​en ramme på forhånd for dit projekt som overhead, der ikke giver konkret forretningsværdi. At starte mindre hjælper dig med at sænke de uforudsete risici, nyde mindre udviklingstid, sænke læringskurven og få projektinteressenters buy-in. For X18p satte jeg kun to mål baseret på mine tidligere møder med J2EE-kode:

  1. Reducer J2EE Handling kodekobling
  2. Reducer gentagelse af kode ved J2EE DAO-lag

Samlet set vil jeg give bedre kvalitetskode og reducere de samlede omkostninger til udvikling og vedligeholdelse ved at øge min produktivitet. Med det gennemgår vi to gentagelser af trin 2 til 6 for at nå disse mål.

Reducer kodekobling

Trin 2. Analyser tidligere J2EE-applikationsarkitektur

Hvis en J2EE-applikationsramme er på plads, skal vi først se, hvordan den kan forbedres. Det er klart, at det ikke giver mening at starte fra bunden. For X18p, lad os se på et typisk J2EE Struts-applikationseksempel, vist i figur 1.

Handling opkald XXXManagerog XXXManager opkald XXXDAOs. I et typisk J2EE-design, der indeholder stivere, har vi følgende ting:

  • HttpServlet eller en struts Handling lag, der håndterer HttpForespørgsel og HttpResponse
  • Forretningslogiklag
  • Dataadgangslag
  • Domænelag, der kortlægges til domæneenhederne

Hvad er der galt med ovenstående arkitektur? Svaret: tæt kobling. Arkitekturen fungerer fint, hvis logikken er inde Handling er simpelt. Men hvad nu hvis du har brug for adgang til mange EJB (Enterprise JavaBeans) komponenter? Hvad hvis du har brug for adgang til webtjenester fra forskellige kilder? Hvad hvis du har brug for adgang til JMX (Java Management Extensions)? Har Struts et værktøj, der hjælper dig med at finde disse ressourcer fra struts-config.xml fil? Svaret er nej. Struts er beregnet til at være en web-tier-only ramme. Det er muligt at kode Handlings som forskellige klienter, og ring til backend via Service Locator-mønsteret. Men hvis du gør det, blandes to forskellige typer kode ind Handling's udføre () metode.

Den første type kode vedrører web-tier HttpForespørgsel/HttpResponse. For eksempel henter kode HTTP-formulardata fra ActionForm eller HttpForespørgsel. Du har også kode, der angiver data i en HTTP-anmodning eller HTTP-session og videresender dem til en JSP-side (JavaServer Pages), der skal vises.

Den anden kodetype vedrører dog forretningsgraden. I Handling, påberåber du dig også backend-kode som f.eks EJBObject, et JMS (Java Message Service) -emne eller endda JDBC (Java Database Connectivity) datakilder og henter resultatdataene fra JDBC-datakilderne. Du kan bruge mønsteret Service Locator i Handling for at hjælpe dig med at slå op. Det er også muligt for Handling kun for at henvise til et lokalt POJO (almindeligt gammelt Java-objekt) xxxManager. Ikke desto mindre et backend-objekt eller xxxManager's signaturer på metodeniveau udsættes for Handling.

Sådan Handling fungerer, ikke? Naturen af Handling er en servlet, der formodes at bekymre sig om, hvordan man tager data ind fra HTML og indstiller data til HTML med en HTTP-anmodning / session. Det grænseflader også til det forretningslogiske lag for at hente eller opdatere data fra det lag, men i hvilken form eller protokol, Handling kunne bryde sig mindre.

Som du kan forestille dig, når en Struts-applikation vokser, kan du ende med stramme referencer imellem Handlings (Web tier) og business managers (business tier) (se de røde streger og pile i figur 1).

For at løse dette problem kan vi overveje de åbne rammer på markedet - lad dem inspirere vores egen tænkning, inden vi får indflydelse. Spring Framework kommer på min radarskærm.

Trin 3. Sammenlign alternative rammer

Kernen i Spring Framework er et koncept kaldet BeanFactory, hvilket er en god opslagsfabriksimplementering. Det adskiller sig fra Service Locator-mønsteret, idet det har en Inversion-of-Control (IoC) -funktion, der tidligere blev kaldt Injektionsafhængighed. Ideen er at få et objekt ved at ringe til din ApplicationContext's getBean () metode. Denne metode slår Spring-konfigurationsfilen op for objektdefinitioner, opretter objektet og returnerer en java.lang.Objekt objekt. getBean () er godt til objektopslag. Det ser ud til, at kun en objektreference, ApplicationContext, skal der henvises til i Handling. Dette er dog ikke tilfældet, hvis vi bruger det direkte i Handling, fordi vi skal kaste getBean ()returnerer objekttypen tilbage til EJB / JMX / JMS / webserviceklienten. Handling skal stadig være opmærksom på backend-objektet på metodeniveau. Tæt kobling findes stadig.

Hvis vi ønsker at undgå en objekt-metode-niveau-reference, hvad mere kan vi bruge? Naturligt, service, kommer til at tænke på. Service er et allestedsnærværende, men neutralt koncept. Alt kan være en tjeneste, ikke nødvendigvis kun de såkaldte webtjenester. Handling kan også behandle en statsløs session bønnemetode som en tjeneste. Det kan også behandle at kalde et JMS-emne som at forbruge en tjeneste. Den måde, vi designer på at forbruge en tjeneste på, kan være meget generisk.

Med strategi formuleret, fare spottet og risikoreduceret fra ovenstående analyse og sammenligning kan vi anspore vores kreativitet og tilføje et tyndt servicemæglerlag for at demonstrere det serviceorienterede koncept.

Trin 4. Udvikl og refaktor

For at implementere den serviceorienterede tankegang i kode skal vi overveje følgende:

  • Servicemæglerlaget tilføjes mellem web-niveauet og forretnings-niveauet.
  • Konceptuelt, en Handling kalder kun en anmodning om forretningsservice, som videresender anmodningen til en service router. Service routeren ved, hvordan man tilslutter forretningsserviceanmodninger til forskellige tjenesteudbyders controllere eller adaptere ved at finde en XML-fil, der kortlægger en tjeneste, X18p-config.xml.
  • Tjenesteudbyderens controller har specifik viden om at finde og påberåbe sig de underliggende forretningstjenester. Her kunne forretningstjenester være alt fra POJO, LDAP (letvægts biblioteksadgangsprotokol), EJB, JMX, COM og webtjenester til COTS (kommerciel fra hylden) produkt-API'er. X18p-config.xml skal levere tilstrækkelige data til at hjælpe tjenesteudbyderens controller med at få arbejdet gjort.
  • Udnyt foråret til X18ps interne objektopslag og referencer.
  • Byg serviceudbyders controllere trinvist. Som du vil se, jo flere tjenesteudbydere der implementeres, jo mere integrationseffekt har X18p.
  • Beskyt eksisterende viden som Struts, men hold øjnene åbne for nye ting, der kommer op.

Nu sammenligner vi Handling kode før og efter anvendelse af den serviceorienterede X18p-ramme:

Struts Action uden X18p

 offentlig ActionForward-udførelse (ActionMapping-kortlægning, ActionForm-form, HttpServletRequest-anmodning, HttpServletResponse-svar) kaster IOException, ServletException {... UserManager userManager = ny UserManager (); String userIDRetured = userManager.addUser ("John Smith") ...} 

Struts Action med X18p

offentlig ActionForward-udførelse (ActionMapping-kortlægning, ActionForm-form, HttpServletRequest-anmodning, HttpServletResponse-svar) kaster IOException, ServletException {... ServiceRequest bsr = this.getApplicationContext (). getBean ("businessServiceRequest"); bsr.setServiceName ("Brugertjenester"); bsr.setOperation ("addUser"); bsr.addRequestInput ("param1", "addUser"); String userIDRetured = (String) bsr.service (); ...} 

Spring understøtter opslag til forretningsserviceanmodningen og andre objekter, herunder eventuelle POJO-ledere.

Figur 2 viser, hvordan Spring-konfigurationsfilen, applicationContext.xml, understøtter opslag af businessServiceRequest og serviceRouter.

I ServiceRequest.java, det service() metode kalder simpelthen Spring for at finde service routeren og videregiver sig selv til routeren:

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

Tjenestereuteren i X18p dirigerer brugertjenester til forretningslogiklaget med X18p-config.xml's hjælp. Nøglepunktet er, at Handling kode behøver ikke at vide, hvor eller hvordan brugertjenester implementeres. Det skal kun være opmærksom på reglerne for forbrug af tjenesten, såsom at skubbe parametrene i den rigtige rækkefølge og kaste den rigtige returtype.

Figur 3 viser segmentet af X18p-config.xml der giver information om service kortlægning, hvilke ServiceRouter vil slå op i X18p.

For brugertjenester er servicetypen POJO. ServiceRouter opretter en POJO-tjenesteudbydercontroller til at håndtere serviceanmodningen. Denne POJO er springObjectId er userServiceManager. POJO-tjenesteudbyderens controller bruger Spring til at slå op i denne POJO med springObjectId. Siden userServiceManager peger på klassetype X18p.framework.UserPOJOManager, det BrugerPOJOManager klasse er den applikationsspecifikke logiske kode.

Undersøge ServiceRouter.java:

 public Object-rute (ServiceRequest serviceRequest) kaster undtagelse {// / 1. Læs al kortlægning fra XML-fil, eller hent den fra Factory // Config config = xxxx; // 2. Hent servicetype fra config. String businessServiceType = Config.getBusinessServiceType (serviceRequest.getServiceName ()); // 3. Vælg den tilsvarende router / håndterer / controller for at håndtere det. hvis (businessServiceType.equalsIgnoreCase ("LOCAL-POJO")) {POJOController pojoController = (POJOController) Config.getBean ("POJOController"); pojoController.process (serviceRequest); } ellers hvis (businessServiceType.equalsIgnoreCase ("WebServices")) {String endpoint = Config.getWebServiceEndpoint (serviceRequest.getServiceName ()); WebServicesController ws = (WebServicesController) Config.getBean ("WebServicesController"); ws.setEndpointUrl (slutpunkt); ws.process (serviceRequest); } ellers hvis (businessServiceType.equalsIgnoreCase ("EJB")) {EJBController ejbController = (EJBController) Config.getBean ("EJBController"); ejbController.process (serviceRequest); } ellers {// TODO System.out.println ("Ukendte typer, det er op til dig, hvordan du håndterer det i rammen"); } // Det er det, det er din ramme, du kan tilføje enhver ny ServiceProvider til dit næste projekt. returnere null; } 

Ovenstående routing if-else-blok kunne omformuleres til et kommandomønster. Det Konfig objekt giver opslag til foråret og X18p XML-konfigurationen. Så længe gyldige data kan hentes, er det op til dig, hvordan du implementerer opslagsmekanismen.

Under forudsætning af en POJO-manager, TestPOJOBusinessManager, implementeres, er POJO-tjenesteudbyderens controller (POJOServiceController.java) ser derefter efter tilføj bruger () metode fra TestPOJOBusinessManager og påberåber det med refleksion (se koden tilgængelig fra Resources).

Ved at indføre tre klasser (BusinessServiceRequester, ServiceRouterog ServiceProviderController) plus en XML-konfigurationsfil, har vi en serviceorienteret ramme som et proof-of-concept. Her Handling har ingen viden om, hvordan en tjeneste implementeres. Det bekymrer sig kun om input og output.

Kompleksiteten ved at bruge forskellige API'er og programmeringsmodeller til at integrere forskellige tjenesteudbydere er beskyttet mod Struts-udviklere, der arbejder på nettet. Hvis X18p-config.xml er designet på forhånd som en servicekontrakt, Struts og backend-udviklere kan arbejde samtidigt efter kontrakt.

Figur 4 viser arkitekturens nye udseende.

Jeg opsummerede de almindelige tjenesteudbydere og implementeringsstrategier i tabel 1. Du kan nemt tilføje flere.

Tabel 1. Implementeringsstrategier for almindelige tjenesteudbyders controllere