Las tres categorías de estructuras de una arquitectura de software
Cuando empecé a estudiar arquitectura de software, una de las cosas que más me costaba era entender qué significaba exactamente cada una de las cajas que aparecían en los diagramas. Una caja podía representar un módulo, un componente, un servicio, un proceso o incluso un servidor. Con las flechas ocurría algo parecido: podían representar una dependencia, una llamada, un mensaje o una relación entre el software y la infraestructura.
Después de leer Software Architecture in Practice, de Len Bass, Paul Clements y Rick Kazman, encontré una clasificación que me resulta bastante útil para poner un poco de orden en todo esto. Los autores hablan de tres categorías de estructuras arquitectónicas: estructuras de módulos, estructuras de componentes y conectores y estructuras de asignación.
La idea me parece interesante porque no intenta definir una única forma de representar una arquitectura. Una arquitectura de software puede observarse desde diferentes perspectivas, y cada estructura nos ayuda a responder preguntas diferentes sobre el mismo sistema.
Estructuras de módulos
Las primeras son las module structures, que describen el sistema desde el punto de vista de sus unidades de implementación. Un módulo es una unidad de software que tenemos que construir, adquirir o mantener y, dependiendo del sistema, puede ser una clase, un paquete, una capa, un proyecto o cualquier otra unidad que utilicemos para organizar el código.
Por ejemplo, si estoy construyendo una aplicación de comercio electrónico, podría dividirla de esta manera:
ECommerce
│
├── Catalog
├── Orders
├── Payments
└── Users
En este caso estoy describiendo una estructura de módulos. Lo que me interesa saber es cómo he dividido el software y qué relación existe entre esas divisiones. Puedo preguntarme qué responsabilidad tiene cada módulo, de qué otros módulos depende, qué módulos puede utilizar o qué partes tendré que modificar cuando cambie una funcionalidad.
Esto último es especialmente interesante porque las estructuras de módulos resultan muy útiles para razonar sobre la modificabilidad del sistema. Si mañana tengo que cambiar la forma en la que calculo los impuestos, por ejemplo, quiero poder identificar rápidamente qué módulos están relacionados con esa funcionalidad y qué otras partes dependen de ellos.
Una estructura de módulos me permite entender cómo está organizado el software que tengo que construir y mantener.
Cuando trabajo con módulos estoy describiendo fundamentalmente una estructura estática. Puedo estar hablando de código que todavía ni siquiera se está ejecutando. Un proyecto de .NET, por ejemplo, puede formar parte de una estructura de módulos independientemente de que en ese momento haya un proceso ejecutándolo.
Por eso, cuando estés mirando una estructura de módulos, la pregunta que te interesa hacerte es bastante sencilla: ¿cómo está organizado el software que tengo que construir?
Estructuras de componentes y conectores
La segunda categoría cambia de perspectiva. Las component-and-connector structures, que normalmente se abrevian como estructuras C&C, describen elementos que tienen comportamiento en tiempo de ejecución y las interacciones entre ellos.
Aquí hay una distinción que me parece importante dejar clara porque es fácil interpretarla mal. El libro no presenta módulos, componentes y conectores como tres categorías independientes. Componentes y conectores forman conjuntamente una única categoría de estructuras arquitectónicas: las estructuras component-and-connector.
Los componentes representan los elementos que participan en la ejecución y los conectores representan los mecanismos mediante los cuales esos elementos interactúan. Por ejemplo, podría tener una arquitectura como esta:
┌───────────────┐
│ Web App │
└───────┬───────┘
│
HTTPS
│
▼
┌───────────────┐
│ API │
└───────┬───────┘
│
SQL
│
▼
┌───────────────┐
│ Database │
└───────────────┘
Aquí puedo identificar tres componentes: la aplicación web, la API y la base de datos. Entre ellos tengo conectores que representan HTTPS y SQL. Pero lo importante no es únicamente identificar las cajas y las flechas, sino entender qué pregunta estoy intentando responder.
Mientras que una estructura de módulos me muestra cómo está organizado el software, una estructura C&C me muestra qué elementos participan en la ejecución y cómo interactúan.
La diferencia se ve todavía mejor cuando comparamos módulos y componentes. Imagina que tengo tres módulos llamados Orders, Payments y Users. Puedo terminar ejecutándolos todos dentro de un único proceso:
┌─────────────────────┐
│ Backend │
│ │
│ Orders │
│ Payments │
│ Users │
└─────────────────────┘
Desde el punto de vista de módulos tengo tres elementos, pero desde el punto de vista de componentes puedo tener uno solo. Un módulo no tiene por qué convertirse en un componente independiente durante la ejecución.
Esto es algo que te conviene tener presente cuando leas un diagrama arquitectónico. No intentes averiguar qué es una caja únicamente por su nombre. Antes de interpretar el elemento, intenta descubrir qué estructura está representando.
Los conectores también son importantes porque nos permiten describir cómo se relacionan los componentes. Un conector puede representar una llamada, un mensaje, un evento, una comunicación mediante una cola, un mecanismo de publicación y suscripción, el acceso a un repositorio compartido o un protocolo de comunicación.
Por ejemplo, en una arquitectura basada en eventos podríamos tener:
Component A
│
│ publish
▼
Broker
│
│ subscribe
▼
Component B
Aquí estamos describiendo una estructura C&C porque estamos hablando de elementos que participan en la ejecución y de las interacciones que se producen entre ellos. Esta perspectiva resulta especialmente útil cuando queremos razonar sobre cuestiones como la comunicación, la concurrencia, el rendimiento o la disponibilidad.
Los componentes y los conectores nos permiten describir la estructura del sistema mientras está funcionando, no simplemente cómo está organizado su código.
Estructuras de asignación
La tercera categoría es probablemente la que más dudas puede generar cuando intentamos traducirla al español. En el libro se utiliza el término allocation y podemos encontrar traducciones como localización, asignación o distribución. Después de darle unas vueltas, creo que asignación describe mejor el concepto que se quiere expresar.
La razón es que una allocation structure no consiste simplemente en indicar dónde está algo. El concepto es más amplio: describe relaciones entre elementos software y elementos que no son software.
Por ejemplo:
Orders
│
│ allocated to
▼
Application Server 01
Aquí estoy asignando un elemento software a un elemento de infraestructura. Pero no tengo por qué estar hablando exclusivamente de hardware. También puedo relacionar elementos software con sistemas de archivos, redes, equipos de desarrollo u otros elementos del entorno.
Por ejemplo, podría tener una relación como esta:
Módulo Orders
│
▼
Equipo de desarrollo de Pedidos
En este caso no estoy diciendo dónde se ejecuta el módulo. Estoy indicando qué equipo tiene asignada su responsabilidad de desarrollo.
Por eso, para mí, «localización» se queda demasiado corto para expresar todo lo que engloba el término allocation; «asignación» resulta una traducción más apropiada.
Allocation no es lo mismo que deployment
Aquí aparece otra distinción que conviene tener presente. Cuando hablamos de allocation es bastante habitual pensar inmediatamente en despliegue, porque una de las situaciones más evidentes en las que asignamos software a elementos externos es precisamente cuando decidimos dónde se ejecutará.
Podríamos tener, por ejemplo:
┌─────────────────────┐
│ Servidor Web │
│ │
│ Web App │
└──────────┬──────────┘
│
HTTPS
│
▼
┌─────────────────────┐
│ Servidor API │
│ │
│ Backend │
└──────────┬──────────┘
│
SQL
│
▼
┌─────────────────────┐
│ Servidor BD │
│ │
│ Database │
└─────────────────────┘
Aquí estoy haciendo una asignación de elementos software a elementos de infraestructura. Sin embargo, deployment y allocation no son sinónimos: el despliegue es un caso particular de una estructura de asignación.
Esto es importante porque, si reduzco allocation a «dónde se despliega cada cosa», estoy perdiendo una parte importante del concepto. Las estructuras de asignación pueden utilizarse también para representar relaciones con otros elementos del entorno de desarrollo o mantenimiento.
Por eso, cuando vuelvas a encontrarte con el término allocation, intenta pensar en algo más amplio que simplemente «dónde se despliega».
Las tres perspectivas juntas
Llegados a este punto, podemos resumir las tres categorías mediante tres preguntas:
| Estructura | Pregunta |
|---|---|
| Module | ¿Cómo está organizado el software? |
| Component-and-connector | ¿Qué elementos participan en la ejecución y cómo interactúan? |
| Allocation | ¿Cómo se relacionan los elementos software con su entorno? |
Imagina que tenemos una aplicación de comercio electrónico. Desde el punto de vista de módulos podríamos tener:
ECommerce
│
├── Catalog
├── Orders
├── Payments
└── Users
Desde el punto de vista de componentes y conectores, la misma aplicación podría representarse así:
Web App
│
HTTPS
│
▼
Backend
│
SQL
│
▼
Database
Y desde el punto de vista de asignación podríamos indicar que el backend se ejecuta en un determinado servidor:
Backend
│
│ allocated to
▼
Application Server
Es el mismo sistema, pero estamos haciendo preguntas diferentes sobre él. Lo que cambia entre las tres estructuras no es necesariamente el sistema que estamos describiendo, sino la perspectiva desde la que lo estamos observando.
Esto explica también por qué dos diagramas de una misma aplicación pueden parecer completamente diferentes y, sin embargo, los dos ser correctos. Uno puede estar describiendo módulos y otro componentes y conectores. Otro puede estar describiendo el despliegue. No están compitiendo entre sí ni tienen por qué contener exactamente las mismas cajas.
No hay un único diagrama que explique toda la arquitectura
Esta es probablemente la idea que más me interesa recordar.
Cuando digo que una aplicación tiene una determinada arquitectura, puedo estar hablando de muchas cosas diferentes. Puedo estar interesado en cómo está organizado el código, en qué elementos participan en la ejecución, en cómo se comunican, en qué servidores se ejecutan o incluso en qué equipo de desarrollo es responsable de cada parte.
Intentar meter toda esa información en un único diagrama suele terminar creando un dibujo difícil de entender.
Cada estructura arquitectónica sirve para responder preguntas diferentes sobre el sistema.
Una estructura de módulos puede ayudarme a entender la organización del código y razonar sobre la modificabilidad. Una estructura C&C puede ayudarme a entender cómo funciona el sistema durante la ejecución y a razonar sobre comunicación, concurrencia o rendimiento. Una estructura de asignación puede ayudarme a analizar cuestiones relacionadas con el despliegue, la infraestructura o la organización del trabajo.
No están compitiendo entre sí. Están describiendo diferentes aspectos de la misma arquitectura.
El error de hacer coincidir las cajas
Una consecuencia práctica de entender esto es que ya no tengo que buscar una correspondencia uno a uno entre las diferentes estructuras.
Puedo tener cuatro módulos:
Orders
Payments
Users
Catalog
y que todos formen parte de un único componente:
┌───────────────────┐
│ Backend │
└───────────────────┘
También puedo tener varios componentes que se ejecuten en el mismo servidor:
┌─────────────────────────┐
│ Server 01 │
│ │
│ Web App │
│ API │
│ Worker │
└─────────────────────────┘
Y puedo tener un componente asignado a una infraestructura completamente diferente:
API
│
▼
Cloud Instance
No necesito que las cajas de las diferentes estructuras tengan una correspondencia uno a uno.
Esta idea puede parecer obvia cuando la ves así, pero creo que es bastante fácil olvidarla cuando empiezas a diseñar una arquitectura y quieres que todos los diagramas encajen perfectamente entre sí.
Por eso, cuando diseñes o leas una arquitectura, intenta no preguntarte únicamente «¿qué es esta caja?». Antes de eso, pregúntate:
¿Qué estructura estoy mirando?
Una vez que sabes qué tipo de estructura tienes delante, resulta mucho más sencillo interpretar qué significa cada caja y cada flecha.
Una forma sencilla de recordarlo
Si dentro de unos meses vuelves a este artículo y ya no recuerdas exactamente cómo funcionaban las tres categorías, creo que puedes quedarte con tres preguntas.
Cuando estés mirando una estructura de módulos, pregúntate:
¿Cómo he dividido el software que tengo que construir?
Cuando estés mirando una estructura de componentes y conectores, pregúntate:
¿Qué elementos participan en la ejecución y cómo interactúan?
Y cuando estés mirando una estructura de asignación, pregúntate:
¿Cómo relaciono esos elementos software con el entorno en el que se desarrollan, ejecutan o mantienen?
Para mí, esa es la parte realmente útil de esta clasificación.
No se trata de memorizar tres términos, sino de disponer de tres formas diferentes de mirar el mismo sistema.
Y cuando dibujes una arquitectura, antes de añadir otra caja o flecha, intenta preguntarte qué estás intentando explicar con ella. Quizá descubras que el problema no era que te faltase otro diagrama, sino que estabas intentando responder tres preguntas diferentes con uno solo.