Diseñe un marco de aplicación J2EE simple orientado a servicios

Hoy en día, los desarrolladores están inundados de marcos de código abierto que ayudan con la programación J2EE: Struts, Spring, Hibernate, Tiles, Avalon, WebWorks, Tapestry u Oracle ADF, por nombrar algunos. Muchos desarrolladores encuentran que estos marcos no son la panacea para sus problemas. El hecho de que sean de código abierto no significa que sean fáciles de cambiar y mejorar. Cuando un marco se queda corto en un área clave, se dirige solo a un dominio específico o simplemente está inflado y es demasiado caro, es posible que deba construir su propio marco sobre él. Construir un marco como Struts no es una tarea trivial. Pero desarrollar gradualmente un marco que aproveche Struts y otros marcos no tiene por qué serlo.

En este artículo, le muestro cómo desarrollar X18p (Xiangnong 18 Palm, llamado así por un legendario y poderoso luchador de kung fu), un marco de muestra que aborda dos problemas comunes ignorados por la mayoría de los marcos J2EE: acoplamiento estrecho y DAO inflado (objeto de acceso a datos) código. Como verá más adelante, X18p aprovecha Struts, Spring, Axis, Hibernate y otros marcos en varias capas. Con suerte, con pasos similares, puede implementar su propio marco con facilidad y hacerlo crecer de un proyecto a otro.

El enfoque que adopto para desarrollar este marco utiliza conceptos del Rational Unified Process (RUP) de IBM. Sigo estos pasos:

  1. Establezca metas simples inicialmente
  2. Analizar la arquitectura de la aplicación J2EE existente e identificar los problemas
  3. Compare marcos alternativos y seleccione el que sea más simple para construir
  4. Desarrolle código de forma incremental y refactorice con frecuencia
  5. Reúnase con el usuario final del marco y recopile comentarios con regularidad
  6. Prueba, prueba, prueba

Paso 1. Establezca metas simples

Es tentador establecer metas ambiciosas e implementar un marco de vanguardia que resuelva todos los problemas. Si tiene suficientes recursos, no es una mala idea. Por lo general, el desarrollo de un marco por adelantado para su proyecto se considera un costo indirecto que no proporciona un valor comercial tangible. Empezar con algo pequeño le ayuda a reducir los riesgos imprevistos, disfrutar de menos tiempo de desarrollo, reducir la curva de aprendizaje y conseguir la aceptación de las partes interesadas del proyecto. Para X18p, establezco solo dos objetivos basados ​​en mis encuentros pasados ​​con el código J2EE:

  1. Reducir el Actionacoplamiento de código J2EE
  2. Reducir la repetición de código en la capa J2EE DAO

En general, quiero proporcionar un código de mejor calidad y reducir el costo total de desarrollo y mantenimiento aumentando mi productividad. Con eso, pasamos por dos iteraciones de los Pasos 2 al 6 para alcanzar esos objetivos.

Reducir el acoplamiento de código

Paso 2. Analizar la arquitectura de la aplicación J2EE anterior

Si existe un marco de aplicación J2EE, primero debemos ver cómo se puede mejorar. Evidentemente, empezar de cero no tiene sentido. Para X18p, veamos un ejemplo típico de aplicación J2EE Struts, que se muestra en la Figura 1.

Actionllamadas XXXManagery XXXManagerllamadas XXXDAOs. En un diseño típico de J2EE que incorpora Struts, tenemos los siguientes elementos:

  • HttpServleto una Actioncapa de Struts que maneja HttpRequestyHttpResponse
  • Capa de lógica empresarial
  • Capa de acceso a datos
  • Capa de dominio que se asigna a las entidades de dominio

¿Qué pasa con la arquitectura anterior? La respuesta: acoplamiento estrecho. La arquitectura funciona bien si la lógica Actiones simple. Pero, ¿qué sucede si necesita acceder a muchos componentes EJB (Enterprise JavaBeans)? ¿Qué sucede si necesita acceder a servicios web desde varias fuentes? ¿Qué sucede si necesita acceder a JMX (Java Management Extensions)? ¿Tiene Struts una herramienta que le ayude a buscar esos recursos en el struts-config.xmlarchivo? La respuesta es no. Struts está destinado a ser un marco de solo nivel web. Es posible codificar Actions como varios clientes y llamar al back-end a través del patrón de localizador de servicios. Sin embargo, al hacerlo se mezclarán dos tipos diferentes de código en Actionel execute()método.

El primer tipo de código se relaciona con el nivel web HttpRequest/ HttpResponse. Por ejemplo, el código recupera datos del formulario HTTP de ActionFormo HttpRequest. También tiene un código que establece datos en una solicitud HTTP o sesión HTTP y los reenvía a una página JSP (JavaServer Pages) para mostrar.

Sin embargo, el segundo tipo de código se relaciona con el nivel empresarial. En Action, también invoca código de back-end, como EJBObjectun tema JMS (Java Message Service), o incluso fuentes de datos JDBC (Java Database Connectivity) y recupera los datos de resultados de las fuentes de datos JDBC. Puede utilizar el patrón de localizador de servicios Actionpara ayudarle a realizar la búsqueda. También es posible Actionhacer referencia solo a un POJO local (antiguo objeto Java simple) xxxManager. Sin embargo, un objeto de backend o xxxManagerlas firmas de nivel de método están expuestas Action.

Así es como Actionfunciona, ¿verdad? La naturaleza de Actiones un servlet que se supone que debe preocuparse por cómo tomar datos de HTML y establecerlos en HTML con una solicitud / sesión HTTP. También interactúa con la capa de lógica empresarial para obtener o actualizar datos de esa capa, pero en qué forma o protocolo, Actionpodría importar menos.

Como puede imaginar, cuando una aplicación de Struts crece, puede terminar con estrechas referencias entre Actions (nivel web) y gerentes comerciales (nivel comercial) (consulte las líneas rojas y flechas en la Figura 1).

Para resolver este problema, podemos considerar los marcos abiertos en el mercado, dejar que inspiren nuestro propio pensamiento antes de generar un impacto. Spring Framework aparece en la pantalla de mi radar.

Paso 3. Comparar marcos alternativos

El núcleo de Spring Framework es un concepto llamado BeanFactory, que es una buena implementación de fábrica de búsqueda. Se diferencia del patrón de localizador de servicios en que tiene una función de inversión de control (IoC) anteriormente denominada dependencia de inyección . La idea es conseguir un objeto llamando a su ApplicationContext's getBean()método. Este método busca definiciones de objetos en el archivo de configuración de Spring, crea el objeto y devuelve un java.lang.Objectobjeto. getBean()es bueno para búsquedas de objetos. Parece que solo se ApplicationContextdebe hacer referencia a una referencia de objeto`` en el Action. Sin embargo, ese no es el caso si lo usamos directamente en el Action, porque debemos getBean()devolver el tipo de objeto de retorno al cliente de servicio EJB / JMX / JMS / Web.Actionaún debe ser consciente del objeto backend a nivel de método. Todavía existe un acoplamiento estrecho.

Si queremos evitar una referencia a nivel de método de objeto, ¿qué más podemos usar? Naturalmente, el servicio , viene a la mente. El servicio es un concepto omnipresente pero neutral. Cualquier cosa puede ser un servicio, no necesariamente solo los llamados servicios web. Actiontambién puede tratar el método de un bean de sesión sin estado como un servicio. También puede tratar la llamada a un tema JMS como consumir un servicio. La forma en que diseñamos para consumir un servicio puede ser muy genérica.

Con la estrategia formulada, el peligro detectado y el riesgo mitigado a partir del análisis y la comparación anteriores, podemos estimular nuestra creatividad y agregar una capa delgada de intermediarios de servicios para demostrar el concepto orientado al servicio.

Paso 4. Desarrollar y refactorizar

Para implementar el pensamiento conceptual orientado a servicios en el código, debemos considerar lo siguiente:

  • La capa de intermediario de servicios se agregará entre el nivel web y el nivel empresarial.
  • Conceptualmente, un solo Actionllama a una solicitud de servicio empresarial, que pasa la solicitud a un enrutador de servicio. El router de servicios sabe cómo conectar las solicitudes de servicio de negocio a diferentes controladores de servicios o adaptadores por buscar un archivo XML de mapeo de servicios, X18p-config.xml.
  • The service provider controller has specific knowledge of finding and invoking the underlying business services. Here, business services could be anything from POJO, LDAP (lightweight directory access protocol), EJB, JMX, COM, and Web services to COTS (commercial off the shelf) product APIs. X18p-config.xml should supply sufficient data to help the service provider controller get the job done.
  • Leverage Spring for X18p's internal object lookup and references.
  • Build service provider controllers incrementally. As you will see, the more service provider controllers implemented, the more integration power X18p has.
  • Protect existing knowledge such as Struts, but keep eyes open for new things coming up.

Now, we compare the Action code before and after applying the service-oriented X18p framework:

Struts Action without 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 complejidad de usar varias API y modelos de programación para integrar varios proveedores de servicios está protegida de los desarrolladores de Struts que trabajan en el nivel Web. Si X18p-config.xmlestá diseñado por adelantado como un contrato de servicio, Struts y los desarrolladores de backend pueden trabajar simultáneamente por contrato.

La Figura 4 muestra el nuevo aspecto de la arquitectura.

Resumí los controladores de proveedores de servicios comunes y las estrategias de implementación en la Tabla 1. Puede agregar más fácilmente.

Tabla 1. Estrategias de implementación para controladores de proveedores de servicios comunes