Diseño de patrones de aplicaciones basadas en web

Estoy diseñando una aplicación simple basada en web. Soy nuevo en este dominio basado en la web. Necesité su consejo con respecto a los patrones de diseño, por ejemplo, cómo debería distribuirse la responsabilidad entre los Servlets, los criterios para crear un nuevo Servlet, etc.

En realidad, tengo pocas entidades en mi página de inicio y, para cada una de ellas, tengo pocas opciones, como agregar, editar y eliminar. Anteriormente estaba usando un Servlet por opciones como Servlet1 para agregar entity1, Servlet2 para edit entity1 y así sucesivamente y de esta manera terminamos teniendo una gran cantidad de servlets.

Ahora estamos cambiando nuestro diseño. Mi pregunta es cómo elegir exactamente cómo elegir la responsabilidad de un servlet. ¿Deberíamos tener un Servlet por entidad que procesará todas sus opciones y reenviar la solicitud a la capa de servicio? ¿O deberíamos tener un servlet para toda la página que procesará la solicitud de toda la página y luego reenviarla a la capa de servicio correspondiente? Además, si el objeto de solicitud reenvió a la capa de servicio o no.

Una aplicación web decente consiste en una combinación de patrones de diseño. Mencionaré solo los más importantes.


Modelo del controlador View Model

El patrón de diseño central (arquitectónico) que le gustaría usar es el patrón Modelo-Vista-Controlador . El controlador se representará mediante un servlet que (en) crea / usa directamente un modelo y una vista específicos en función de la solicitud. El modelo debe ser representado por clases Javabean. Esto a menudo es divisible en el Modelo de Negocio que contiene las acciones (comportamiento) y el Modelo de Datos que contiene los datos (información). La Vista debe ser representada por archivos JSP que tienen acceso directo al Modelo ( Datos ) por EL (Lenguaje de Expresión).

Luego, hay variaciones basadas en cómo se manejan las acciones y los eventos. Los populares son:

  • MVC basado en solicitud (acción) : es el más simple de implementar. El Modelo ( Comercial ) trabaja directamente con los objetos HttpServletRequest y HttpServletResponse . Debe reunir, convertir y validar los parámetros de solicitud (en su mayoría) usted mismo. La Vista se puede representar mediante HTML / CSS / JS simple y no mantiene el estado en todas las solicitudes. Así es como funciona Spring MVC , Struts and Stripes .

  • MVC basado en componentes : esto es más difícil de implementar. Pero terminas con un modelo más simple y una vista en la que toda la API del Servlet “en bruto” se abstrae por completo. No debe tener la necesidad de reunir, convertir y validar los parámetros de solicitud usted mismo. El controlador realiza esta tarea y establece los parámetros de solicitud recostackdos, convertidos y validados en el modelo . Todo lo que necesita hacer es definir métodos de acción que trabajen directamente con las propiedades del modelo. La Vista está representada por “componentes” en el sabor de taglibs JSP o elementos XML que a su vez genera HTML / CSS / JS. El estado de la Vista para las solicitudes subsiguientes se mantiene en la sesión. Esto es particularmente útil para la conversión del lado del servidor, la validación y los eventos de cambio de valor. ¡Así es como entre otros JSF , Wicket y Play! trabajos.

Como nota al margen, hobbying con un marco MVC de cosecha propia es un ejercicio de aprendizaje muy agradable, y lo recomiendo, siempre y cuando lo mantenga para fines personales / privados. Pero una vez que seas profesional, se recomienda encarecidamente elegir un marco existente en lugar de reinventar el tuyo. Aprender un marco existente y bien desarrollado lleva a largo plazo menos tiempo que el desarrollo y mantenimiento de un marco sólido por sí mismo.

En la explicación detallada a continuación, me limitaré a solicitar MVC basado en que es más fácil de implementar.


Patrón de controlador frontal ( patrón de mediador )

Primero, la parte del Controlador debe implementar el patrón del Controlador Frontal (que es un tipo especializado de patrón de Mediator ). Debe constar de un solo servlet que proporciona un punto de entrada centralizado para todas las solicitudes. Debe crear el Modelo en función de la información disponible en la solicitud, como pathinfo o servletpath, el método y / o los parámetros específicos. El Modelo de Negocio se llama Action en el siguiente ejemplo de HttpServlet .

 protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { Action action = ActionFactory.getAction(request); String view = action.execute(request, response); if (view.equals(request.getPathInfo().substring(1)) { request.getRequestDispatcher("/WEB-INF/" + view + ".jsp").forward(request, response); } else { response.sendRedirect(view); // We'd like to fire redirect in case of a view change as result of the action (PRG pattern). } } catch (Exception e) { throw new ServletException("Executing action failed.", e); } } 

La ejecución de la acción debe devolver algún identificador para ubicar la vista. Lo más simple sería usarlo como nombre de archivo del JSP. Asigne este servlet a un url-pattern específico en web.xml , p. Ej., /pages/* , *.do o incluso solo *.html .

En el caso de los patrones de prefijo como por ejemplo /pages/* , podría invocar URL como http://example.com/pages/register , http://example.com/pages/login , etc. y proporcionar /WEB-INF/register.jsp , /WEB-INF/login.jsp con las acciones GET y POST apropiadas. El register piezas, el login , etc. están entonces disponibles por request.getPathInfo() como en el ejemplo anterior.

Cuando usa patrones de sufijos como *.do , *.html , etc., puede invocar URL como http://example.com/register.do , http://example.com/login.do , etc. y debe cambiar los ejemplos de código en esta respuesta (también ActionFactory ) para extraer el register y las partes de login mediante request.getServletPath() .


Patrón de estrategia

La Action debe seguir el patrón de Estrategia . Debe definirse como un tipo abstracto / de interfaz que debe hacer el trabajo en función de los argumentos pasados del método abstracto (esta es la diferencia con el patrón Comando , en el que el tipo abstracto / interfaz debe hacer el trabajo basado en el argumentos que se han transmitido durante la creación de la implementación).

 public interface Action { public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception; } 

Es posible que desee hacer que la Exception más específica con una excepción personalizada como ActionException . Es solo un ejemplo básico de patada de salida, el rest depende de ti.

Aquí hay un ejemplo de una LoginAction que (como su nombre lo dice) inicia sesión en el usuario. El User sí es a su vez un modelo de datos . La vista es consciente de la presencia del User .

 public class LoginAction implements Action { public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception { String username = request.getParameter("username"); String password = request.getParameter("password"); User user = userDAO.find(username, password); if (user != null) { request.getSession().setAttribute("user", user); // Login user. return "home"; // Redirect to home page. } else { request.setAttribute("error", "Unknown username/password. Please retry."); // Store error message in request scope. return "login"; // Go back to redisplay login form with error. } } } 

Patrón de método de fábrica

ActionFactory debe seguir el patrón de método de Fábrica . Básicamente, debe proporcionar un método creacional que devuelva una implementación concreta de un tipo abstracto / interfaz. En este caso, debe devolver una implementación de la interfaz de Action en función de la información proporcionada por la solicitud. Por ejemplo, el método y pathinfo (pathinfo es la parte después del contexto y la ruta del servlet en la URL de solicitud, excluyendo la cadena de consulta).

 public static Action getAction(HttpServletRequest request) { return actions.get(request.getMethod() + request.getPathInfo()); } 

Las actions a su vez, deberían ser un Map estático / de toda la aplicación que contenga todas las acciones conocidas. Depende de usted cómo llenar este mapa. Código difícil:

 actions.put("POST/register", new RegisterAction()); actions.put("POST/login", new LoginAction()); actions.put("GET/logout", new LogoutAction()); // ... 

O configurable en función de un archivo de configuración de propiedades / XML en el classpath: (pseudo)

 for (Entry entry : configuration) { actions.put(entry.getKey(), Class.forName(entry.getValue()).newInstance()); } 

O dinámicamente basado en un escaneo en el classpath para las clases que implementan una cierta interfaz y / o anotación: (pseudo)

 for (ClassFile classFile : classpath) { if (classFile.isInstanceOf(Action.class)) { actions.put(classFile.getAnnotation("mapping"), classFile.newInstance()); } } 

Tenga en cuenta que para crear una Action “no hacer nada” para el caso no hay mapeo. Deje que, por ejemplo, devuelva directamente request.getPathInfo().substring(1) luego.


Otros patrones

Esos fueron los patrones importantes hasta ahora.

Para ir un paso más allá, puedes usar el patrón Fachada para crear una clase Context que a su vez envuelve los objetos de solicitud y respuesta y ofrece varios métodos de conveniencia para delegar en los objetos de solicitud y respuesta y pasarlos como argumento en Action#execute() método en su lugar. Esto agrega una capa abstracta adicional para ocultar la API de Servlet sin procesar. Básicamente, debes terminar con cero import javax.servlet.* declaraciones import javax.servlet.* En cada implementación de Action . En términos JSF, esto es lo que están haciendo las clases FacesContext y ExternalContext . Puedes encontrar un ejemplo concreto en esta respuesta .

Luego está el patrón de estado para el caso en el que desea agregar una capa de abstracción adicional para dividir las tareas de recostackción de parámetros de solicitud, convertirlos, validarlos, actualizar los valores del modelo y ejecutar las acciones. En términos JSF, esto es lo que está haciendo LifeCycle .

Luego está el patrón Compuesto para el caso en el que desea crear una vista basada en componentes que se puede adjuntar con el modelo y cuyo comportamiento depende del estado del ciclo de vida basado en la solicitud. En términos JSF, esto es lo que representa el UIComponent .

De esta forma, puede evolucionar poco a poco hacia un marco basado en componentes.


Ver también:

  • Ejemplos de patrones de diseño GoF en las bibliotecas centrales de Java
  • Diferencia entre Request MVC y Component MVC
  • Mostrar JDBC ResultSet en HTML en la página JSP usando el patrón MVC y DAO
  • ¿Qué componentes son MVC en el marco JSF MVC?
  • Controlador JSF, Servicio y DAO

En el patrón MVC golpeado, el Servlet es “C” – controlador.

Su trabajo principal es hacer una evaluación de solicitud inicial y luego enviar el procesamiento basado en la evaluación inicial al trabajador específico. Una de las responsabilidades del trabajador puede ser configurar algunos beans de capa de presentación y reenviar la solicitud a la página JSP para generar HTML. Por lo tanto, solo por este motivo, debe pasar el objeto de solicitud a la capa de servicio.

Sin embargo, no comenzaría a escribir clases cruda de Servlet . El trabajo que hacen es muy predecible y repetitivo, algo que el marco hace muy bien. Afortunadamente, hay muchos candidatos disponibles y probados en el tiempo (en orden alfabético): Apache Wicket , Java Server Faces , Spring por nombrar algunos.

En mi humilde opinión, no hay mucha diferencia en el caso de la aplicación web si lo mira desde el ángulo de asignación de responsabilidad. Sin embargo, mantenga la claridad en la capa. Guarde algo exclusivamente para el propósito de la presentación en la capa de presentación, como el control y el código específicos de los controles web. Simplemente mantenga sus entidades en la capa empresarial y todas las funciones (como agregar, editar, eliminar), etc. en la capa empresarial. Sin embargo, los renderiza en el navegador para ser manejado en la capa de presentación. Para .Net, el patrón ASP.NET MVC es muy bueno en términos de mantener las capas separadas. Mire en el patrón MVC.

He utilizado el marco de puntales y me resulta bastante fácil de aprender. Al usar el marco de struts, cada página de su sitio tendrá los siguientes elementos.

1) Una acción que se utiliza se invoca cada vez que se actualiza la página HTML. La acción debe rellenar los datos en el formulario cuando la página se carga por primera vez y maneja las interacciones entre la interfaz de usuario web y la capa empresarial. Si está utilizando la página jsp para modificar un objeto java mutable, una copia del objeto java debe almacenarse en el formulario en lugar del original para que los datos originales no se modifiquen a menos que el usuario guarde la página.

2) El formulario que se utiliza para transferir datos entre la acción y la página jsp. Este objeto debe consistir en un conjunto de getter y setters para los atributos que deben ser accesibles para el archivo jsp. El formulario también tiene un método para validar datos antes de que persista.

3) Una página jsp que se utiliza para representar el HTML final de la página. La página jsp es un híbrido de HTML y tags de puntales especiales utilizados para acceder y manipular datos en el formulario. Aunque struts permite a los usuarios insertar código Java en archivos jsp, debe tener mucho cuidado al hacerlo, ya que hace que su código sea más difícil de leer. El código Java dentro de los archivos jsp es difícil de depurar y no se puede probar en unidades. Si te encuentras escribiendo más de 4-5 líneas de código java dentro de un archivo jsp, probablemente el código deba moverse a la acción.

La excelente respuesta de BalusC cubre la mayoría de los patrones para aplicaciones web.

Algunas aplicaciones pueden requerir Chain-of-responsibility_pattern

En el diseño orientado a objetos, el patrón de cadena de responsabilidad es un patrón de diseño que consiste en una fuente de objetos de comando y una serie de objetos de procesamiento. Cada objeto de procesamiento contiene lógica que define los tipos de objetos de comando que puede manejar; el rest pasa al siguiente objeto de procesamiento en la cadena.

Use mayúsculas / minúsculas para usar este patrón:

Cuando el controlador para procesar una solicitud (comando) es desconocido y esta solicitud se puede enviar a múltiples objetos. En general, establece sucesor a objeto. Si el objeto actual no puede manejar la solicitud o procesar la solicitud parcialmente y reenviar la misma solicitud al objeto sucesor .

Preguntas / artículos útiles de SE:

¿Por qué alguna vez usaría una Cadena de Responsabilidad sobre un Decorador?

¿Usos comunes para la cadena de responsabilidad?

patrón de cadena de responsabilidad de oodesign

chain_of_responsibility de sourcemaking