Documente Academic
Documente Profesional
Documente Cultură
2. Introducción
2.1. Propósito del documento
Especificar los artefactos de arquitectura para construir los diagramas de secuencia de diseño y los
diagramas de clase de diseño en la asignatura Diseño de Sistemas.
Este enfoque de arquitectura ayude a entender cualquier otro enfoque de arquitectura de cualquier
aplicación, aunque se debe entender que según la tecnología o frameworks que se utilice puede cambiar
bastante.
Dicho de otra forma, un framework de desarrollo suele ser un esqueleto reusable de arquitectura o
aplicación base genérica en un lenguaje dado que permite utilizarse en muchas variantes de negocio. De
esta forma los desarrolladores no se preocupan por cómo resolver esos problemas técnicos o
arquitectónicos, sino que se enfocan más en los problemas de lógica del negocio de la aplicación que están
construyendo.
No es necesario ni requerido utilizar un framework para construir una aplicación, pero casi siempre
se usa uno ya que son construidos con calidad, han sido probados al construir miles de aplicaciones y han
ido evolucionando a lo largo de mucha experiencia y años de trabajo.
Nota: Como se ve el framework spring tiene muchos modulos, librerías y frameworks por lo que permite
muchas variantes de cómo utilizarlo y combinar estos módulos.
A los efectos de cumplir con el enfoque de arquitectura para soluciones de Diseño de Sistemas, en el resto
del documento nos vamos focalizar en uso simple y práctico de este framework teniendo en cuenta solo los
componentes más utilizados.
3) consulta o modifica el
2) Recibe Controlador modelo
1) Ingresa URL Solicitud
http://mydomain.com/about
/
4) carga la vista Modelo
adecuada para esta
solicitud y le pasa los
6) Respuesta datos u objetos mínimos
a la solicitud del modelo que la vista
necesita mostrar.
Controlador: Recibe solicitudes de trabajo y las procesa para lograr la respuesta adecuada.
• No debe resolver lógica de negocio ni hacer cálculos.
• Delega toda la responsabilidad de la lógica del negocio a los objetos del modelo.
• Delega toda la responsabilidad de presentación de datos y usabilidad a la vista.
• Es responsable de resolver lo haga falta de un caso de uso haciendo lo mínimo posible y delegando
al máximo a los objetos del modelo (por ejemplo Dao y Dominio) y la vista.
• En base a condiciones decide cual es la vista que se debe cargar y controla como y hacia donde
continúa el flujo de la aplicación.
• Carga la vista que corresponde para la respuesta y le pasa los datos u objetos del modelo que
necesita.
• Dentro de una aplicación, no hay un único controlador, sino que hay muchos. Si bien no hay una
regla y depende del diseño, es común que haya un controlador por cada caso de uso o historia de
usuario.
• De un mismo controlador pueden existir muchas instancias funcionando para atender muchos
usuarios del mismo caso de uso al mismo tiempo.
Modelo: Conjunto de objetos que resuelven la lógica del negocio y el dominio.
• Incluye clases de dominio con métodos del negocio.
• Incluye clases de software como Repositorios o DAOs (Data Access object) para conexión a bases
de datos y clases de Servicio.
• Suele haber varias capas dentro del modelo.
• No resuelve nada ligado a la presentación de datos al usuario.
Vista: Componentes responsables de generar el contenido que se va a mostrar al usuario final (ejemplo:
HTML).
• No debe resolver lógica de negocio ni hacer cálculos.
• Recibe los datos u objetos del modelo que necesita consultar para mostrar al usuario.
• Resuelven toda la lógica ligada a la presentación e interfaz de usuario como ocultar, mostrar una
imagen, bloquear, habitar un textbox, cambiar el título de un Label, validar que un campo es
requerido, cambiar un color según la acción de un usuario, bloquear o habilitar un botón, etc.
• Según las acciones del usuario como hacer clic en un botón, pueden realizar una nueva solicitud al
controlador.
3) Maneja y resuelve el
pedido interactuando
2) Según el URL, invoca a con otros objetos
un método específico de como repositorios,
un controlador. Cada DAO y objetos del
método de cada dominio y servicios.
controlador tiene
definido a que URL
1) Solicitud por URL procesa. Controlador
Front Controller
10) Respuesta (componente Controlador 1 Controlador 2 4) Crea un
HTML provisto por Spring) model, vista met1(p) met1(p) objeto
met2(p) met2(p)
“model” solo
Controlador 3 con los datos
6) Delega como se 5) Retorna el met1(p)
9) Retorna va a mostrar la u objetos del
model y el met2(p)
el control model vista. dominio que
nombre
y la vista necesita la
de la vista
procesad vista.
7) Carga la vista que se
a como debe
que indicó el
HTML.
controlador y lo cargar.
procesa con los
objetos que Modelo
Vista están dentro de
model.
vista 1 Dominio DAO
vista 2 BD
8) Consulta los datos
del model que
recibió que necesita
para la presentación.
Todas las peticiones HTTP se canalizan a través del front controller. En casi todos los frameworks
MVC que siguen este patrón, el front controller no es más que un servlet de java cuya implementación es
propia del framework. En el caso de Spring, la clase DispatcherServlet.
El front controller averigua, normalmente a partir de la URL, a qué Controller hay que llamar para
servir la petición. Para esto se usa un HandlerMapping que no aparece en el diagrama.
Se llama al Controller, que ejecuta la lógica de negocio, obtiene los resultados y los devuelve al
servlet, encapsulados en un objeto del tipo Model. Además se devolverá el nombre lógico de la vista a
mostrar (normalmente devolviendo un String, como en JSF).
Un ViewResolver (no aparece en el diagrama) se encarga de averiguar el nombre físico de la vista
que se corresponde con el nombre lógico del paso anterior.
Finalmente, el front controller (el DispatcherServlet) redirige la petición hacia la vista, que muestra los
resultados de la operación realizada.
En realidad, el procesamiento es más complejo. Nos hemos saltado algunos pasos por simplicidad y
para dar una mayor claridad. Por ejemplo, en Spring se pueden usar interceptores, que son como los filtros
del API de servlets, pero adaptados a Spring MVC. Estos interceptores pueden pre y postprocesar la petición
alrededor de la ejecución del Controller. No obstante, todas estas cuestiones deben quedar por fuerza fuera
de una breve introducción a Spring MVC como la de estas páginas.
Desde el punto de vista del desarrollador, sólo se debe preocupar por implementar los controladores,
vista y objetos del modelo ya que los demás componentes son provistos por Spring para el funcionamiento
del MVC.
En la parte de Modelo de MVC hemos incluido los objetos del Dominio y clases de software llamadas
DAO (Data Access Object) que encapsulan el acceso a la base de datos. Este es un enfoque de arquitectura,
pero no el único y se puede resolver de otras maneras.
Tampoco es requerido que el Controlador las utilice, pero es lo más común. A veces también se
incluyen algunas otras clases como servicios para reutilizar lógica entre varios controladores.
Para simplificar la arquitectura vamos asumir que el Controlador va a interactuar con los DAO y
objetos del dominio directamente.
7. Dominio
En el dominio están las clases de negocio. Estas clases resuelven la mayor parte de lógica de
negocio y tienen responsabilidades de negocio bien definidas y de única responsabilidad.
7.2.1. Persistencia
Todos los objetos de cualquier clase deben estar en memoria RAM para poder ejecutarse, sin
embargo, casi todos los objetos del dominio además son persistentes, esto quiere decir que las instancias de
una clase de dominio se deben guardar en un medio físico para que puedan ser recuperadas más adelante
en cualquier momento más allá de la vida o ejecución de la aplicación (por ejemplo, luego de que la
aplicación o servidor se reinicia) ya que representan la información que el negocio genera y/o necesita para
funcionar. En nuestro caso vamos a asumir que las instancias van a ser guardadas como registros de alguna
tabla en una base de datos. A su vez cuando se desee cargar una instancia de una clase de dominio se debe
leer el registro correspondiente de la tabla de la base de datos y cargarla en memoria como un objeto.
Otras clases como el Controlador y DAO no necesitan ser persistentes.
Los objetos del dominio no son responsables de la persistencia. Dicho de otra forma, no saben que
son persistidos ni que existe la persistencia. Para eso se aplica el patrón DAO para resolver la persistencia
de esos objetos sin que se enteren. A su vez los objetos del dominio no invocan métodos a los DAO ni saben
de su existencia. Los objetos del dominio tampoco saben de la existencia de otros objetos del dominio a
menos que tengan una asociación con estos, instancien algunos o se los pasen como referencia.
Las clases Cliente y Localidad son clases persistentes del dominio. Por ejemplo para crear un DAO
para la clase de dominio Cliente, tenemos que crear una interfaz ClienteDao que herede de la interfaz
JpaRepositroy. T e ID son tipos de datos no definidos en JpaRepository. Al extender la interfaz JpaRepository
se debe definir que T va a ser de la clase Cliente y que ID va a ser de tipo Integer.
Ahora cuando usemos un ClienteDao este tendrá todos los métodos de JpaRepository (pero reemplazando T
por Cliente e ID por Integer) mas los métodos específicos para buscar clientes que uno le quiera agregar a
ClienteDao.
Es decir que una instancia de ClienteDao será capaz responder a métodos como:
Método descripción
buscarClientesPorLocalidad(l:Localidad) Método adicional que agrega ClienteDao. Hay
que definir una anotación con una consulta JPQL:
SELECT c FROM Cliente c where c.localidad =
?1
buscarClientesPorNombre(nombre:String) Método adicional que agrega ClienteDao. Hay
que definir una anotación con una consulta JPQL:
SELECT c FROM Cliente c where c.nombre like
?1
getOne(id:Integer):Cliente devuelve una instancia de Cliente según su id
findAll():Cliente[*] devuelve una colección con todas las instancias
que existan de clientes.
save(c:Cliente) guarda el objeto c:Cliente en la base de datos. Si
no existe lo inserta, si ya existe lo actualiza.
existsById(id:Integer) :Boolean Devuelve true si existe un Cliente con ese id.
count():Integer Devuleve la cantidad de objetos de ese tipo que
existen guardados.
delete(c:Cliente) Elimina ese cliente de la base de datos.
deleteAll() Elimina todos los objetos cliente.
deleteById(id:Integer) Elimina un cliente con ese id de la base de datos.
saveAll(entidades:Cliente[*]) Guarda todos los clientes de una colección en la
base de datos.
En este caso el ControladorAltaCliente tiene una asociación con ClienteDao y tiene un atributo
cdao:ClienteDao.
Cuando Spring se ejecuta lee las clases existentes y sus anotaciones y sabe que tiene que crear una
implementación de ClienteDao para la clase Cliente que tiene un Id de tipo Integer en el atributo idCliente y lo
inyecta en la variable cdao del ControladorAltaCliente. Además la implementación que crea Spring sabe que
cuando se ejecute un método como buscarClientesPorLocalidad(l:Localidad) deberá ejecutar la consulta
JPQL: SELECT c FROM Cliente c where c.localidad = ?1. O sea, que el Controlador nunca sabe cuál es la
implementación real sino que sabe que utiliza algo de tipo ClienteDao y responde a los métodos que
necesita. El desarrollador no se preocupa cómo funciona ya que se lo delega a Spring.
SELECT ... FROM ... [WHERE ...] [GROUP BY ... [HAVING ...]] [ORDER BY ...]
Las primeras 2 cláusulas SELECT y FROM son requeridas. Las demás son opcionales.
SELECT c FROM Cliente c WHERE c.name LIKE ‘?1%’ OR c.apellido LIKE ‘?2%’
Una consulta para obtener los objetos Localidad cuya descripción empiece con ‘a’ sin importar mayúsculas o
minúsculas y esté ordenada por código postal:
SELECT c FROM Cliente c a JOIN c.localidad loc WHERE loc.codPostal > 1000
SELECT DISTINCT loc FROM Localidad loc a JOIN loc.clientes c WHERE loc.codPostal LIKE
‘%0’ OR c.id > 20
Ahora teniendo en cuenta las siguientes clases:
Esta consulta obtiene los autores que han publicado libros en la editorial 'XYZ Press'
En el ejemplo anterior se requiere las entidades satisfagan la condición del join. La segunda consulta
devuelve sólo las localidades que tengan al menos un cliente. Si hubiera una localidad con código postal 100
pero sin clientes, no sería devuelta ya que requiere que exista la relación.
Si se quisiera considerar localidades que no tengan clientes se debe usar LEFT JOIN.
SELECT DISTINCT loc FROM Localidad loc LEFT JOIN loc.clientes c WHERE loc.codPostal LIKE
‘%0’ OR c.id > 20
de columnas y funciones como proyección. Uno puede hacer lo mismo en JPQL seleccionando un conjunto
de atributos de entidades, escalares o funciones como proyección.
8.2.5.1. Entidad
La forma más común de usar la proyección es por entidad, es decir que el select devuelva un objeto
completo de una clase dada por cada registro que encuentre.
8.2.5.2. Distinct
SELECT DISTINCT loc FROM Localidad loc JOIN Cliente c WHERE c.nombre LIKE ‘a%’
Esta consulta devuelve las localidades donde hay clientes cuyo nombre empieza con la letra a. Pero en una
localidad (por ejemplo: Rosario) puede haber muchos clientes con esa característica por lo que la consulta
devolverá la localidad Rosario tantas veces como clientes hay con el nombre que empiece con la letra a. Por
eso podemos aplicar el operador DISTINCT para que elimine los duplicados que genera la consulta.
Ejemplo:
SELECT a FROM Autor a JOIN a.libros b JOIN b.editorial e WHERE e.id >1
Devolvería el mismo autor varias veces, mientras que la siguiente consulta elimina los duplicados:
SELECT DISTINCT a FROM Autor a JOIN a.libros b JOIN b.editorial e WHERE e.id >1
• a.id = 10
• a.id <> 10
• a.id > 10
• a.id => 10
• a.id < 10
• a.id <= 10
• a.id BETWEEN 5 and 10
• a.nombre LIKE ‘%ua%’
• a.nombre NOT LIKE ‘%ua%’
• a.nombre LIKE ‘_ua_’
• a.nombre IS NULL
• a.nombre IS NOT NULL
• In: a.nombre IN ('Martin', 'Kent')
En LIKE el caracter % representa cualquier secuencia de caracteres. Este ejemplo restringe que el
resultado de la consulta a los autores cuyo nombre contiene el String “ua” como Juan, Juanjo, Juan Martin,
Luana, etc. También se puede usar “_” para representar solo un caracter. Por ejemplo ‘_ua_’ solo devolvería
“Juan”. También se puede negar con el operador NOT para excluir los clientes con ese tipo de nombre.
SELECT loc FROM Localidad loc WHERE loc.codPostal like ‘2%’ and loc.descripcion IS NOT
NULL and size(loc.clientes) >= 5
8.2.7. Funciones
Las funciones son una característica ponderosa de JPQL y premiten hacer operaciones básicas en las
cláusulas WHERE y SELECT. Se pueden usar las siguientes funciones:
La siguiente consulta obtiene todos los apellidos que empiezan con ‘B’ y cuenta cuantas instancias
hay de cada una:
SELECT a.apellido, COUNT(a) AS cnt FROM Autor a GROUP BY a.apellido HAVING a.apellido
LIKE 'B%'
La siguiente consulta obtiene todos las biografías que contienen la palabra ‘Martin’ y cuenta cuantas
instancias hay de cada una:
SELECT a.bio, COUNT(a) AS cnt FROM Autor a GROUP BY a.bio HAVING a.bio LIKE '%Martin%'
La siguiente consulta devuelve las editoriales que tienen 2 o mas autores:
SELECT DISTINCT e FROM Editorial e JOIN e.libros b JOIN b.autores a GROUP BY e HAVING
count(a) >= 2
La siguiente consulta devuelve las editoriales que tienen publicados 2 o más libros:
SELECT a FROM Autor a JOIN a.libros b GROUP BY a HAVING a.bio LIKE '%Martin%' AND
count(b) >= 2 ORDER BY a.nombre
La siguiente consulta devuelve los autores que han publicado libros para dos o más editoriales distintas:
SELECT c FROM Cliente c WEHERE c.id BETWEEN 50 AND 100 ORDER BY c.apellido ASC, c.nombre
DESC
8.2.10. Sub-consultas
Una sub-consulta es una consulta embebida dentro de otra consulta. Es una característica poderosa
utilizada en SQL. JPQL soporta las sub-consultas sólo dentro de la cláusula WHERE y no el SELECT o
FROM.
Las sub-consultas pueden devolver uno o muchos registros y pueden usar alias definidos en la
consulta contenedora.
La siguiente sub-consulta devuelve la cantidad de libros que ha escrito cada autor a. La consulta principal
devuelve los objetos autor que han escrito uno o más libros.
SELECT a FROM Autor a WHERE (SELECT count(b) FROM Libro b WHERE a MEMBER OF b.autores)>1
8.2.11. Polimorfismo
Cuando se utiliza una estrategia de herencia que soporta consulta polimórficas, una consulta
devuelve todas las instancias de una determinada clase y sus subclases. En el diagrama de clases un Autor
escribe publicaciones que pueden ser libros o artículos web. La clase publicación es abstracta por lo que no
habrá instancias reales de esta clase pero si de Libro y Articulo.
La siguiente consulta devuelve una colección con todos los objetos Articulo y objetos Libro que
existen:
La siguiente consulta devuelve una colección sólo con todos los objetos Articulo que existen pero no
los objetos de tipo Libro:
9. Apéndice
9.1. Principios SOLID
En ingeniería de software, SOLID (Single responsibility, Open-closed, Liskov substitution, Interface
segregation and Dependency inversion) es un acrónimo mnemónico introducido por Robert C. Martin a
comienzos de la década del 2000 que representa cinco principios básicos de la programación orientada a
objetos y el diseño. Cuando estos principios se aplican en conjunto es más probable que un desarrollador
cree un sistema con alta cohesión y bajo acoplamiento que sea fácil de mantener y ampliar con el tiempo.
9.1.2. Open/closed
Principio de abierto/cerrado. La noción de que las “entidades de software deben estar abiertas para
su extensión, pero cerradas para su modificación”.
Principio atribuido a Bertrand Meyer que habla de crear clases extensibles sin necesidad de entrar al
código fuente a modificarlo. Es decir, el diseño debe ser abierto para poderse extender pero cerrado para
poderse modificar. Aunque dicho parece fácil, lo complicado es predecir por donde se debe extender y que
no tengamos que modificarlo. Para conseguir este principio hay que tener muy claro como va a funcionar la
aplicación, por donde se puede extender y como van a interactuar las clases.
Llega un momento en que las necesidades pueden llegar a ser tan imprevisibles que nos topemos
que con los métodos definidos en el interface o en los métodos extensibles, no sean suficientes para cubrir
las necesidades. En este caso no habrá más remedio que romper este principio y refactorizar.
Este principio fue creado por Barbara Liskov y habla de la importancia de crear todas las clases
derivadas para que también puedan ser tratadas como la propia clase base. Cuando creamos clases
derivadas debemos asegurarnos de no reimplementar métodos que hagan que los métodos de la clase base
no funcionases si se tratasen como un objeto de esa clase base.
La Fabrica necesita hacer trabajar a cualquier objeto que responda al método trabajar() mientras que el
Resturant le va a servir comida a cualquier objeto que responda al método comer().
Entonces se pueden definir dos interfaces Trabajador y Comensal (sin implementación) bien específicas
siguiendo el principio de única responsabilidad. De esta forma fabrica y el restaurant no se preocupan de
que clase es la instancia realmente mientras que implementen la interfaz Trabajador o Comensal
respectivamente. Ahí se produce el principio de inversión de dependencia ya que el la frabrica y el
restaurant tienen dependendencias sobre las interfaces y no sobre las clases que las implementen.
La colección trabajadores tendrá una mezcla de instancias de Robot, Operador y SuperTrabajador pero la
Fabrica los ve como Trabajador, ya que es lo único que conoce. De esa forma se cumple con el principio de
subsitución de Liskov ya cualquiera de esas tres instancias son substituibles a los ojos de la Fabrica y
responden a lo que define la interfaz Trabajador. En el caso que no existiera la interfaz Comensal y el método
comer() estuviera en la interfaz Trabajador, el Cliente se vería obligadoa implementar esa interfaz y el
método trabajar() de forma que no haga nada o tire un error. Ahí se produciría una falta a ese principio ya
que el cliente no es sustuible como trabajador ya que realmente no puede trabajar.
Por eso, dado hay 4 clases que responden a los mismos métodos en algunos casos y en otros no, se aplica
el principio de segregacion de interfaz, creando dos interfaces específicas y que cada clase implemente
las interfaces a las que realemente puede dar comportamiento.
Por último, el uso de interfaces permite el principio abierto-cerrado, ya que si quisieramos agregar una
máquina que solo trabaja, no necesitamos cambiar nada de lo que existe (cerrado para modificación) sino
que solo tenemos que crear una clase Maquina con el método trabajar() que implemente la interfaz
Trabajador (abierto para extensión).
9.3. Ejercicio
Siguiendo los principios SOLID, modifique el diseño anterior para agregar:
• Una Maquina que puede trabajar y se le debe cargar combustible.
• Un vehículo que no trabaja pero se le debe cargar combustible.
• Una estación de servicio que carga combustible.
10. Bibliografía
• https://projects.spring.io/spring-framework/#quick-start
• https://martinfowler.com/eaaCatalog/frontController.html
• https://martinfowler.com/eaaCatalog/repository.html
• http://www.jtech.ua.es/j2ee/publico/spring-2012-13/sesion03-apuntes.html
• https://www.martinfowler.com/bliki/AnemicDomainModel.html
• https://docs.spring.io/spring-data/jpa/docs/1.5.0.RELEASE/reference/html/jpa.repositories.html
• https://spring.io/guides/gs/accessing-data-jpa/
• https://docs.spring.io/spring-
data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html
• https://docs.oracle.com/html/E13946_01/ejb3_langref.html
• http://www.oodesign.com/design-principles.html
• https://es.wikipedia.org/wiki/SOLID
• https://www.genbetadev.com/paradigmas-de-programacion/solid-cinco-principios-basicos-de-diseno-
de-clases
• http://www.objectdb.com/java/jpa/query
• https://es.wikipedia.org/wiki/Java_Persistence_Query_Language
• https://www.tutorialspoint.com/es/jpa/jpa_jpql.htm
• https://docs.oracle.com/html/E13946_01/ejb3_langref.html
• https://www.thoughts-on-java.org/jpql/
• https://www.thoughts-on-java.org/complete-guide-inheritance-strategies-jpa-hibernate/