Diseñar es imaginar, diseñar es pensar. El objetivo del diseño del software es imaginar, pensar, esbozar la implementación de los componentes y el software especificados en el análisis condicionado por las decisiones arquitectónicas tomadas.
La arquitectura del sistema es un nivel de diseño que pone el foco en aspectos más allá de los algoritmos, del modelado de datos o de los requisitos funcionales. La arquitectura del software establece las bases, los pilares, la estructura, sobre la cual reposará el resto de componentes. Estos “cimientos” deben ayudar a minimizar el esfuerzo de desarrollo, despliegue y mantenimiento del producto software.
Antes de aterrizar el diseño del software es necesario tomar ciertas decisiones arquitectónicas. En función de ellas, el diseño de componentes de más bajo nivel puede variar. La arquitectura del sistema se puede estudiar bajo los siguientes prismas:
Arquitectura de integración, describe cómo intercambian información los productos;
Arquitectura de componentes, describe los componentes que constituyen un producto;
Arquitectura de desarrollo, describe cómo se distribuyen los componentes que constituyen un producto.
💬 La Arquitectura del Software es la estructura de los componentes de un sistema, sus interrelaciones, y los principios y directrices que rigen su diseño y evolución a lo largo del tiempo
Garlan y Perry, 1995
💬 La arquitectura del software es el conjunto de decisiones de diseño sobre cualquier sistema que evita que sus implementadores y mantenedores ejerzan una creatividad innecesaria
Souza y Cameron, 1998
💬 La arquitectura del software es la organización fundamental de un sistema, encarnada en sus componentes, las relaciones entre ellos y el entorno y los principios que gobiernan su diseño y evolución
ISO 42010, 2007
💬 La arquitectura del software son las decisiones de diseño significativas que dan forma a un sistema, donde la importancia se mide por el costo del cambio
Grady Booch, 2018
Los sistemas de información deben intercambiar información, bien por cuestiones funcionales, de disponibilidad, etc. Estas integraciones entre sistemas pueden realizarse de diferentes maneras, siendo algunas de ellas más o menos adecuadas según las necesidades concretas de cada caso. La arquitectura de integración pone el foco en los pilares básicos sobre los que los productos software construirán sus canales de comunicación.
Las formas más habituales de intercambio de datos entre productos son:
Acceso directo a la base de datos. Un producto obtiene datos de otro accediendo directamente a las tablas donde el otro producto almacena la información. En este escenario, la ausencia de contrato de servicio y la falta de ejecución de reglas de negocio sobre los datos impone un alto acoplamiento lógico (y tecnológico) y baja reusabilidad. Exige al producto consumidor de datos conocer las peculiaridades de la capa de persistencia del propietario de los datos para interactuar con ella. Una evolución o cambio del propietario (por ejemplo, el cambio de nombre de una columna) impacta en el consumidor, obligándole a actualizarse.
Intercambio de ficheros de texto. Los productos generan ficheros de texto que luego intercambian. Debido a su alta interoperabilidad tecnológica y su capacidad para funcionar incluso cuando uno de los productos ha visto degradado su capacidad de servicio, este tipo de integración es muy utilizada. Sin embargo, tiene importantes carencias: no es posible una comunicación síncrona de los datos. Además, el consumidor del fichero accede a una “foto” de la información expuesta por el propietario de los datos, no a los datos “actuales”.
Servicios web. Un servicio web es un puerto de comunicaciones ofrecido por un producto que, utilizando como capa de transporte el protocolo HTTP, ofrece datos en un formato estándar (XML o JSON) a través de mensajes normalizados (SOAP o REST). Al procesar el mensaje recibido el producto propietario de los datos puede ejecutar ciertas reglas de negocio adaptando de esta forma la información.
En algunos escenarios corporativos con fuertes necesidades de integración, la proliferación de productos exige implementar mecanismos para el gobierno de las comunicaciones entre sistemas de información (Arquitectura Orientada a Servicios, SOA). SOA exige el uso de un bus de integración (ESB) que habilita un punto central de gobierno para monitorizar, documentar, transformar y orquestar las comunicaciones entre productos.
Un producto software puede componerse de múltiples aplicativos o elementos reutilizables. Las relaciones que se establecen entre todos estos componentes conforman la Arquitectura de Componentes del producto.
En la siguiente figura se muestra un diagrama donde se aprecian los diferentes componentes de un producto, las relaciones entre ellos y las relaciones con otros sistemas externos.
El término “arquitectura de microservicios” ha surgido en los últimos años para describir una forma particular de diseñar aplicaciones de software como conjuntos de servicios funcionalmente autónomos y desplegables independientemente. La arquitectura basada en microservicios se contrapone a la “arquitectura monolítica” basada en grandes productos software multifuncionales cuyos módulos ni son autónomos ni desplegables de forma independiente.
Los microservicios toman el monolito (gran producto fuertemente acoplado) y lo segmentan funcionalmente. Es decir, los microservicios cortan rebanadas “funcionales” (verticales) del producto, cada uno con su lógica de negocio, su base de datos, etc. Los microservicios suelen requieren trabajar conjuntamente, así los microservicios son “ensamblados” o “mezclados” en un único interfaz de usuario.
La arquitectura de desarrollo ofrece al desarrollador las “estanterías” en las que colocar las clases o componentes codificados. Las “estanterías” son las librerías, carpetas, o paquetes en las que se divide el código. La “estantería” en que colocar cada clase o componente viene dada por sus responsabilidades y relaciones.
Los principales patrones de arquitectura de desarrollo son:
Arquitectura Multinivel. Una arquitectura multi-tier (o n-tier) hace referencia a una arquitectura en la que el sistema es separado en varias capas físicas. Los distintos componentes son ejecutados en máquinas distintas.
Arquitectura Multicapa. Una arquitectura multi-layer (o n-layer) refleja la separación lógica en capas de un sistema software. En este contexto una capa es simplemente un conjunto de clases o componentes que tienen unas responsabilidades relacionadas dentro del sistema. Estas capas están organizadas de forma jerárquica, unas encima de otras. Una capa debe depender solamente de las capas inferiores, pero nunca de las superiores. En el nivel de backend (servidor) lo más común suele ser disponer una capa de servicio, otra de negocio y otra de acceso a datos. Dentro de este tipo de arquitectura podemos diferenciar entre sistemas estrictos y relajados: Un sistema estricto es aquel en el que una capa solo depende directamente de la capa inmediatamente inferior, mientras que en un sistema relajado puede hacerlo de todas las que hay por debajo, aunque no sean contiguas.
Arquitectura en Cebolla. En la arquitectura multicapa, todas las capas dependen de la inferior. La última capa es la de acceso a datos, en último término todas depende de ella, y ella no depende de nadie. La arquitectura multi-capa pone en el “centro”, en el core, de la aplicación el acceso a datos. Sin embargo, en sistemas complejos, el “core” de nuestra aplicación, el núcleo de lo que todo depende debe ser el dominio del negocio. Poner en el “centro”, las reglas del negocio, en vez del acceso a datos, se alinea muy bien con ciertos principios de desarrollo, como el principio de inversión de dependencia. La arquitectura en Cebolla, popularizada por Jeffrey Palermo, pone en el centro el modelo del dominio de negocio.
A grandes rasgos se trata de una arquitectura multicapa construida en torno a un modelo de dominio independiente de todo lo demás. Las dependencias van de fuera hacia dentro, por lo que todo depende de ese modelo de dominio. En contacto con el modelo del dominio habitan los interfaces de repositorio, es decir, las clases que definen el comportamiento del almacenamiento de los datos. Estas clases indican el contrato de los repositorios, pero no los implementan. En las capas siguientes está la lógica de negocio que implementa estas interfaces. Alrededor del núcleo de modelo puede haber un número variable de capas.
Arquitectura Hexagonal. También conocida como “Ports and Adapters”. Definida por Alistair Cockburn (firmante del manifiesto ágil). Al igual que la arquitectura en cebolla, el objetivo de la arquitectura hexagonal es poner en el centro del sistema toda la lógica propia del dominio y definir fronteras claras y mecanismos de transformación con el exterior.
El sistema se compondrá de tres partes bien diferenciadas: la lógica core de la aplicación, los puertos y los adaptadores. Cualquier comunicación con el exterior se hará única y exclusivamente a través de los puertos y adaptadores, que se encargará de la conversión de datos para que dentro de las fronteras todo esté en nuestro idioma. Los puertos son los contratos o interfaces que ofrece el core. Los adaptadores implementan dichos contratos.
Ports and Adapters es más prescriptivo y detallado que la arquitectura en Cebolla definiendo y concretando puertos y adaptadores. Los puertos pueden ser “primarios”, contratos que serán adaptados por repositorios de acceso a datos, presentadores web... o bien puede ser “secundarios”, contratos de comunicación con otros sistemas que serán adaptados por los servicios web.
Ports and Adapters y la “Arquitectura en cebolla” introducen un nuevo punto de vista respecto a las clásicas arquitecturas de finales de los 90s y comienzos de las 2000 basadas en “capas y niveles”. Ponen el foco, no solamente en la creación de “indirecciones” o “saltos” entre capas con diversas responsabilidades, sino además en suavizar el acoplamiento entre capas haciendo que las clases dependan de contratos y no directamente de implementaciones. El principio de “inversión de dependencia” hace referencia a la conveniencia de depender de interfaces, no de implementaciones concretas.
Además de cómo se distribuyen las capas en los niveles físicos, es fundamental establecer el modo en que las capas de presentación se comunicarán con las del negocio. Existen diferentes patrones para separar la lógica de presentación o vista (interfaz de usuario) con la lógica del negocio o modelo (entidades y servicios). Gracias a este desacoplamiento, el programador es capaz de modificar el aspecto visual del producto software sin necesidad de modificar las reglas del negocio. Por ejemplo, un programador puede sustituir una interfaz de usuario web por una interfaz basada en una aplicación de consola sin necesidad de modificar sus clases de negocio, únicamente cambiando las clases de la vista. Los patrones más habituales son el Modelo - Vista - Presentador (MVP) y el Modelo - Vista - Controlador (MVC):
Modelo. Son el conjunto de clases que “modelan” el negocio o dominio. Por ejemplo: Enfermera, Fármaco, Paciente, Dosis, etc. Todas estas clases expresan lenguaje de negocio, en este caso hablan de la administración del medicamento. Estas clases implementan las reglas de negocio.
Vista. Son el conjunto de clases que interactúan directamente con el usuario. Por ejemplo: Aviso, Panel, Cabecera, Menú. Estas clases no expresan lenguaje de negocio. Un aviso puede ser mostrado como una ventana emergente en un navegador o como una línea en rojo en una aplicación de escritorio. La vista es una representación del estado del Modelo en un momento concreto y en el contexto de una acción determinada. Por ejemplo, una factura puede ser mostrada como una página web o puede ser visualizada como un PDF. La página web y el PDF son dos vistas diferentes de la entidad “factura” del Modelo.
Presentador / Controlador. Son clases que actúan como intermediarios entre el Modelo y la Vista. En función de cómo implementan esta comunicación pueden denominarse “controlador” o “presentador”. El presentador o controlador captura las acciones sobre la vista, por ejemplo, la pulsación de un botón, y desencadena ciertas acciones sobre las clases del modelo, por ejemplo, la actualización del atributo “importe” de un objeto “factura”.
🔎 Además de los patrones MVC o MVP existe otra solución para separar el modelo de la vista denominada MVVM (Model - View - ViewModel). También existen soluciones “híbridas” como MVCVM (Model - View - Controller - ViewModel). Cuál es el mejor patrón es un tema controvertido. Aburridos por discusiones interminables y poco fructíferas, los desarrolladores de Google decidieron bautizar el patrón implementado por el framework javascript AngularJS como MVW: Model - View - Whatever (Modelo - Vista - “Lo que sea”).
Referencias:
• Métrica-3 Técnicas. Administración Electrónica del Gobierno de España.
• K.Beck; "Extreme Programming Explained", Addison-Wesley Professional, 1999
• M.Fowler; "UML Distilled. A Brief Guide to the Standard Object Modeling"
• R.C. Martin "Código Limpio", Anaya, 2009
• S.McConnell "Code Complete", Microsoft, 1993
• Autentia. Software Design: Principios y patrones del desarrollo de software
• Martin Fowler, ¿Ha muerto el diseño? 2004
• Jack Reeves, Code As Design, 1992