Progettare un semplice framework applicativo J2EE orientato ai servizi

Oggi, gli sviluppatori sono sommersi da framework open source che aiutano con la programmazione J2EE: Struts, Spring, Hibernate, Tiles, Avalon, WebWorks, Tapestry o Oracle ADF, solo per citarne alcuni. Molti sviluppatori ritengono che questi framework non siano la panacea per i loro problemi. Solo perché sono open source non significa che siano facili da modificare e migliorare. Quando un framework è insufficiente in un'area chiave, si rivolge solo a un dominio specifico o è semplicemente gonfio e troppo costoso, potrebbe essere necessario creare il proprio framework sopra di esso. Costruire un framework come Struts non è un compito banale. Ma lo sviluppo incrementale di un framework che sfrutta Struts e altri framework non deve esserlo.

In questo articolo, ti mostro come sviluppare X18p (Xiangnong 18 Palm, dal nome di un leggendario e potente combattente di kung fu), un framework di esempio che risolve due problemi comuni ignorati dalla maggior parte dei framework J2EE: tight coupling e bloated DAO (data access object) codice. Come vedrai più avanti, X18p sfrutta Struts, Spring, Axis, Hibernate e altri framework a vari livelli. Si spera che, con passaggi simili, sia possibile eseguire il rollio del proprio framework con facilità e svilupparlo di progetto in progetto.

L'approccio che adotto nello sviluppo di questo framework utilizza concetti tratti da Rational Unified Process (RUP) di IBM. Seguo questi passaggi:

  1. Stabilisci inizialmente obiettivi semplici
  2. Analizza l'architettura dell'applicazione J2EE esistente e identifica i problemi
  3. Confronta framework alternativi e seleziona quello con cui è più semplice costruire
  4. Sviluppa codice in modo incrementale e refactoring spesso
  5. Incontra l'utente finale del framework e raccogli feedback regolarmente
  6. Prova, prova, prova

Passaggio 1. Stabilisci obiettivi semplici

Si è tentati di fissare obiettivi ambiziosi e implementare un quadro all'avanguardia che risolva tutti i problemi. Se hai risorse sufficienti, non è una cattiva idea. In generale, lo sviluppo iniziale di un framework per il progetto è considerato un sovraccarico che non fornisce un valore aziendale tangibile. Iniziare in piccolo ti aiuta a ridurre i rischi imprevisti, a risparmiare tempo di sviluppo, ad abbassare la curva di apprendimento e ad ottenere il consenso degli stakeholder del progetto. Per X18p, ho impostato solo due obiettivi in ​​base ai miei precedenti incontri con il codice J2EE:

  1. Ridurre l' Actionaccoppiamento del codice J2EE
  2. Riduci la ripetizione del codice a livello J2EE DAO

Nel complesso, desidero fornire un codice di qualità migliore e ridurre il costo totale di sviluppo e manutenzione aumentando la mia produttività. Con ciò, eseguiamo due iterazioni dei passaggi da 2 a 6 per raggiungere questi obiettivi.

Ridurre l'accoppiamento del codice

Passaggio 2. Analizzare l'architettura dell'applicazione J2EE precedente

Se è presente un framework applicativo J2EE, dobbiamo prima vedere come può essere migliorato. Ovviamente, iniziare da zero non ha senso. Per X18p, diamo un'occhiata a un tipico esempio di applicazione J2EE Struts, mostrato nella Figura 1.

Actionchiama XXXManagere XXXManagerchiama XXXDAOs. In un tipico design J2EE che incorpora Struts, abbiamo i seguenti elementi:

  • HttpServleto un Actionlivello Struts che gestisce HttpRequesteHttpResponse
  • Livello di logica aziendale
  • Livello di accesso ai dati
  • Livello di dominio che mappa alle entità di dominio

Cosa c'è di sbagliato nell'architettura di cui sopra? La risposta: accoppiamento stretto. L'architettura funziona bene se la logica Actionè semplice. Ma cosa succede se è necessario accedere a molti componenti EJB (Enterprise JavaBeans)? Cosa succede se è necessario accedere ai servizi Web da varie fonti? Cosa succede se è necessario accedere a JMX (Java Management Extensions)? Struts ha uno strumento che ti aiuta a cercare quelle risorse dal struts-config.xmlfile? La risposta è no. Struts è pensato per essere un framework solo di livello Web. È possibile codificare Actions come vari client e chiamare il back-end tramite il pattern Service Locator. Tuttavia, così facendo mescolare due diversi tipi di codice a Action's execute()metodo.

Il primo tipo di codice si riferisce al livello Web HttpRequest/ HttpResponse. Ad esempio, il codice recupera i dati del modulo HTTP da ActionFormo HttpRequest. Hai anche un codice che imposta i dati in una richiesta HTTP o una sessione HTTP e li inoltra a una pagina JSP (JavaServer Pages) per la visualizzazione.

Il secondo tipo di codice, tuttavia, si riferisce al livello aziendale. In Action, si richiama anche il codice di backend come EJBObjectun argomento JMS (Java Message Service) o anche le origini dati JDBC (Java Database Connectivity) e si recuperano i dati dei risultati dalle origini dati JDBC. Puoi usare il pattern Service Locator Actionper aiutarti a fare la ricerca. È anche possibile Actionfare riferimento solo a un POJO locale (semplice vecchio oggetto Java) xxxManager. Tuttavia, xxxManagersono esposti un oggetto di backend o le firme a livello di metodo di Action.

È così che Actionfunziona, vero? La natura di Actionè un servlet che dovrebbe preoccuparsi di come prendere i dati dall'HTML e impostare i dati sull'HTML con una richiesta / sessione HTTP. Si interfaccia anche al livello di logica aziendale per ottenere o aggiornare i dati da quel livello, ma in quale forma o protocollo Actionpotrebbe interessare di meno.

Come puoi immaginare, quando un'applicazione Struts cresce, potresti ritrovarti con stretti riferimenti tra Actions (livello Web) e manager aziendali (livello aziendale) (vedere le linee rosse e le frecce nella Figura 1).

Per risolvere questo problema, possiamo considerare le strutture aperte sul mercato: lasciate che ispirino il nostro pensiero prima di avere un impatto. Spring Framework arriva sul mio schermo radar.

Passaggio 3. Confronta framework alternativi

Il nucleo di Spring Framework è un concetto chiamato BeanFactory, che è una buona implementazione della factory di ricerca. Si differenzia dal pattern Service Locator in quanto dispone di una funzionalità IoC (Inversion-of-Control) precedentemente denominata Injection Dependency . L'idea è quella di ottenere un oggetto chiamando il ApplicationContext's getBean()metodo. Questo metodo cerca nel file di configurazione Spring le definizioni degli oggetti, crea l'oggetto e restituisce un java.lang.Objectoggetto. getBean()è utile per le ricerche di oggetti. Sembra che solo un riferimento a un oggetto,, ApplicationContextdebba essere referenziato nel file Action. Tuttavia, questo non è il caso se lo usiamo direttamente in Action, perché dobbiamo getBean()eseguire il cast del tipo di oggetto di ritorno di nuovo al client del servizio EJB / JMX / JMS / Web.Actiondeve ancora essere a conoscenza dell'oggetto backend a livello di metodo. Esiste ancora un accoppiamento stretto.

Se vogliamo evitare un riferimento a livello di metodo oggetto, cos'altro possiamo usare? Naturalmente, il servizio , viene in mente. Il servizio è un concetto onnipresente ma neutro. Qualsiasi cosa può essere un servizio, non necessariamente solo i cosiddetti servizi Web. Actionpuò trattare anche il metodo di un bean di sessione senza stato come un servizio. Può considerare anche chiamare un argomento JMS come consumare un servizio. Il modo in cui progettiamo per consumare un servizio può essere molto generico.

Con la strategia formulata, il pericolo individuato e il rischio mitigato dall'analisi e dal confronto di cui sopra, possiamo stimolare la nostra creatività e aggiungere un sottile livello di broker di servizi per dimostrare il concetto orientato al servizio.

Passaggio 4. Sviluppo e refactoring

Per implementare il concetto orientato ai servizi nel codice, dobbiamo considerare quanto segue:

  • Il livello del broker di servizi verrà aggiunto tra il livello Web e il livello aziendale.
  • Concettualmente, un Actionchiama solo una richiesta di servizio aziendale, che passa la richiesta a un router di servizio. Il router di servizio sa come collegare le richieste di servizi alle imprese a diversi controller erogatori di servizi o adattatori, cercando un file XML di mapping servizio, X18p-config.xml.
  • Il controller del fornitore di servizi ha una conoscenza specifica della ricerca e del richiamo dei servizi aziendali sottostanti. In questo caso, i servizi aziendali potrebbero essere qualsiasi cosa, da POJO, LDAP (protocollo di accesso alla directory leggero), EJB, JMX, COM e servizi Web alle API dei prodotti COTS (commercial off the shelf). X18p-config.xmldovrebbe fornire dati sufficienti per aiutare il controllore del fornitore di servizi a portare a termine il lavoro.
  • Sfrutta Spring per la ricerca di oggetti interni e i riferimenti di X18p.
  • Crea controller per provider di servizi in modo incrementale. Come vedrai, più controller del provider di servizi sono implementati, maggiore è la potenza di integrazione di X18p.
  • Proteggi le conoscenze esistenti come Struts, ma tieni gli occhi aperti per le nuove cose in arrivo.

Ora confrontiamo il Actioncodice prima e dopo aver applicato il framework X18p orientato ai servizi:

Azione Struts senza X18p

 public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)throws IOException, ServletException { ... UserManager userManager = new UserManager(); String userIDRetured = userManager.addUser("John Smith") ... } 

Struts Action with X18p

public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { ... ServiceRequest bsr = this.getApplicationContext().getBean("businessServiceRequest"); bsr.setServiceName("User Services"); bsr.setOperation("addUser"); bsr.addRequestInput("param1", "addUser"); String userIDRetured = (String) bsr.service(); ... } 

Spring supports lookups to the business service request and other objects, including POJO managers, if any.

Figure 2 shows how the Spring configuration file, applicationContext.xml, supports the lookup of businessServiceRequest and serviceRouter.

In ServiceRequest.java, the service() method simply calls Spring to find the service router and passes itself to the router:

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

The service router in X18p routes user services to the business logic layer with X18p-config.xml's help. The key point is that the Action code doesn't need to know where or how user services are implemented. It only needs to be aware of the rules for consuming the service, such as pushing the parameters in the correct order and casting the right return type.

Figure 3 shows the segment of X18p-config.xml that provides the service mapping information, which ServiceRouter will look up in X18p.

For user services, the service type is POJO. ServiceRouter creates a POJO service provider controller to handle the service request. This POJO's springObjectId is userServiceManager. The POJO service provider controller uses Spring to look up this POJO with springObjectId. Since userServiceManager points to class type X18p.framework.UserPOJOManager, the UserPOJOManager class is the application-specific logic code.

Examine ServiceRouter.java:

 public Object route(ServiceRequest serviceRequest) throws Exception { // /1. Read all the mapping from XML file or retrieve it from Factory // Config config = xxxx; // 2. Get service's type from config. String businessServiceType = Config.getBusinessServiceType(serviceRequest.getServiceName()); // 3. Select the corresponding Router/Handler/Controller to deal with it. 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(endpoint); ws.process(serviceRequest); } else if (businessServiceType.equalsIgnoreCase("EJB")) { EJBController ejbController = (EJBController) Config.getBean("EJBController"); ejbController.process(serviceRequest); } else { //TODO System.out.println("Unknown types, it's up to you how to handle it in the framework"); } // That's it, it is your framework, you can add any new ServiceProvider for your next project. return null; } 

The above routing if-else block could be refactored into a Command pattern. The Config object provides the Spring and X18p XML configuration lookup. As long as valid data can be retrieved, it's up to you how to implement the lookup mechanism.

Assuming a POJO manager, TestPOJOBusinessManager, is implemented, the POJO service provider controller (POJOServiceController.java) then looks for the addUser() method from the TestPOJOBusinessManager and invokes it with reflection (see the code available from Resources).

By introducing three classes (BusinessServiceRequester, ServiceRouter, and ServiceProviderController) plus one XML configuration file, we have a service-oriented framework as a proof-of-concept. Here Action has no knowledge regarding how a service is implemented. It cares about only input and output.

La complessità dell'utilizzo di varie API e modelli di programmazione per integrare vari provider di servizi è protetta dagli sviluppatori Struts che lavorano sul livello Web. Se X18p-config.xmlè progettato in anticipo come un contratto di servizio, Struts e gli sviluppatori back-end possono lavorare contemporaneamente per contratto.

La figura 4 mostra il nuovo aspetto dell'architettura.

Ho riassunto i controller dei provider di servizi comuni e le strategie di implementazione nella Tabella 1. È possibile aggiungerne facilmente altri.

Tabella 1. Strategie di implementazione per controller di provider di servizi comuni