Algunos Criterios de Arquitectura en Relación al Sistema de Cómputo

Historial de revisiones
Revisión 1.028/11/2008Diego
Bravo
Primera versión

Tabla de contenidos

1. Introducción
2. Arquitecturas propuestas
3. Criterios de Análisis
3.1. Accesibilidad
3.2. Integración Legacy
3.3. Complejidad no lineal
3.4. Rapidez en las interacciones
3.5. Resistencia a Fallos
3.6. Complejidad de la Coordinación
4. Cuadro Resumen

1. Introducción

Este documento describe algunos criterios funcionales de arquitecturas típicas utilizadas para desplegar cualquier pieza de código (función, objeto, componente, librería) desde el punto de vista de su inter-relación con los elementos principales del sistema de cómputo: procesos, sistema operativo, red.

2. Arquitecturas propuestas

La siguiente figura ilustra cuatro arquitecturas bastante genéricas utilizadas para desplegar el los programas o sus constituyentes. La primera (arquitectura "1") corresponde a desarrollar el código generando archivos "objeto" de tipo estático, los cuales son "enlazados" por el desarrollador para formar un "ejecutable" que contiene dichos objetos. Esta arquitectura es quizá la más utilizada para proyectos muy pequeños. La interacción entre los "componentes" incluídos (por ejemplo, funciones y métodos) es transparente al desarrollador e inmediata. Es casi como haber desarrollado todo en un único gran archivo de código fuente.

La arquitectura "2" es una variante que se suele combinar con lo anterior; aquí el ejecutable no contiene el código que hemos desarrollado, sino "referencias" a este código (shared libraries) las cuales se cargan sólo al momento de la ejecución, pudiéndose compartir la memoria empleada con este fin entre varios ejecutables.

La arquitecura "3" considera distintos ejecutables para alojar el código desarrollado, por lo cual se requiere además un mecanismo de comunicación entre "componentes", generalmente facilitado por el sistema operativo (por ejemplo, colas.)

Esta comunicación no es necesariamente transparente para el desarrollador, por lo que se convierte en un aspecto crítico en el diseño del programa.

Finalmente, la arquitectura "4" extiende lo anterior a distintos computadores, nuevamente gracias a facilidades de comunicación entre procesos que brindan el sistemas operativos de ambos computadores.

Arquitecturas a comparar

3. Criterios de Análisis

A continuación algunos criterios de diseño, y cómo lo satisface cada arquitectura.

3.1. Accesibilidad

El componente requiere ser accesible por otros, o publicable como servicio?

Accesibilidad

En este caso se trata de analizar en qué medida un componente puede ser utilizado/reutilizado por otros. Aquí es relativamente claro que las arquiteturas "4" y "3" (en ese orden) proporcionan facilidades largamente superiores a las otras. En particular, el componente puede ser empleado por otros que satisfacen el contrato o protocolo establecido para intercambiar la información.

Por lo general se recurre a una aplicación adicional que coordina este intercambio de información (middleware.)

Entre los casos "1" y "2", el segundo es relativamente superior en tanto (al igual que "3" y "4") permite realizar actualizaciones en el "componente" accedido, independientemente de los componentes "cliente". Esto únicamente no es posible en el caso "1", que requiere un enlace entre el "componente" así como los objetos consituyentes (los objetos cliente) cosa que puede ser impráctica.

Es por eso, por ejemplo, que practicamente en toda plataforma moderna, el sistema operativo brinda una multitud de librerías compartidas con las que todas las aplicaciones se enlazan. Los parches del sistema operativo generalmente actualizan estas librerías, en beneficio de los programas del usuario (clientes de estas librerías.)

3.2. Integración Legacy

El componente se debe integrar con código ya existente (legacy)?

Integración Legacy

En este caso se trata de analizar en qué medida un componente puede interactuar con otras aplicaciones "legacy" para las cuales muy posiblemente no tenemos la capacidad de realizar modificaciones.

En este caso por lo general las opciones "4" y "3" son superiores puesto que generalmente el código "legacy" proporciona algún tipo de interface capaz de interconectarse a la red o a un middleware.

Esto es generalmente más probable de lograr a que el código "legacy" se enlace o recompile con nuestros objetos.

3.3. Complejidad no lineal

Cuando tenemos componentes muy complejos, la totalidad del sistema se incrementa excesivamente? (otra forma de especificarlo es, dados los objetos con complejidad C1 y C2, el sistema tiene una complejidad total:

C(1,2) >> C1 + C2

(>> significa "mucho mayor".) Lo ideal es que permanezca aproximadamente en:

C(1,2) = C1 + C2

Complejidad no lineal

En este caso se debe hacer un análisis caso por caso. En líneas generales como muestra la figura (arquitecturas "4" y "3") es más sencillo lidiar con un componente complejo a la vez, que con varios al mismo tiempo (es decir, divide y vencerás.) Sin embargo, diversos factores pueden hacer poco atractiva esta arquirectura, por ejemplo:

  • La complejidad de las interacciones entre los objetos, pueden obligar a crear un contrato/protocolo de mensajería muy complejo y difícil de depurar
  • La complejidad de las interacciones no admite retardos dedicados a transferir mensajes
  • Muchas rutinas del problema ya están desarrolladas, pero no facilitan la separación entre objetos independientes (posiblemente por un mal diseño, o un diseño que queda obsoleto con el tiempo.)

3.4. Rapidez en las interacciones

La interacción entre componentes puede generar retardos inaceptables para diversas aplicaciones.

Velocidad en interacciones

Dependiendo de la frecuencia con que interactúen los componentes, puede haber una gran cantidad de invocaciones mutuas, lo que obliga en ciertos escenarios a que las interacciones sean muy veloces. En ese sentido las arquitecturas "1" y "2" son superiores a "3", la cual a su vez es superior a "4".

De otro modo, la flexible separación entre procesos distintos sólo se justifica cuando la aplicación no hará un excesivo número de invocaciones por unidad de tiempo. Por el contrario, se prefiere reducir estas invocaciones, y tratar que cada componente realize actividades relativamente complejas y completas de manera independiente.

Esto debe considerarse caso por caso. Por ejemplo, podríamos tener un componente en un proceso auxiliar, encargado de todas las interacciones con una base de datos en beneficio otro proceso principal?

Para esto necesariamente debemos tener estimados de los tiempos involucrados (y posiblemente tomar tiempos en prototipos desarrollados con dicho fin.) Por ejemplo, podemos observar que un arquitectura tipo "3", el tiempo de ida y vuelta de un mensaje a través de una cola es de unos pocos milisegundos, mientras que las consultas de base de datos tardan entre una centésima y una décima de segundo. Esto quiere decir que es razonable realizar la separación indicada, pues el tiempo total no se reduciría considerablemente si empleamos una arquitectura monolítica como "1" (unas milésimas son despreciables con relación a algunas centésimas.)

Por el contrario, si el acceso a la base de datos está también en el orden de milisegundos, entonces el tiempo de "mensajería" de las arquitecturas "3" y "4" sí es considerable. En esta situación debemos analizar hasta qué punto este retardo de base de datos es crítico. Quizá la base de datos es invocada sólo después de que un usuario ingresa datos manualmente, y por lo tanto el retardo (del orden de milésimas) no le será perceptible; situación muy distinta ocurre si la aplicación dispara un conjunto de consultas secuenciales y relacionadas cada vez que el usuario ingresa algunos datos. Por ejemplo, supongamos que se lanzan en promedio 50 consultas. Si la base de datos tarda en promedio 8 ms por consulta, el tiempo total de base de datos alcanza a 400 ms; esto significa 0.4 segundos, lo cual es ligeramente perceptible por el usuario. Si tuvieramos que considerar mensajería, por ejemplo, de arquiectura tipo "3", tendríamos aproximadamente un monto duplicado de tiempo (0.8 segundos, perfectamente perceptible.) Una arquitectura como "4" podría subir esto fácilmente a 50 ms/consulta, es decir unos incómodos 2.5 segundos para el usuario, por cada "click".

3.5. Resistencia a Fallos

Si un componente falla, en qué medida este fallo impacta en el sistema total?

Resistencia a fallos

El código suele fallar. Aquí no se trata de evitar que esto ocurra, sino de minimizar las consecuencias de este suceso.

Una arquitectura como "4" tiene el potencial de permitir desplegar copias de respaldo de nuestros componentes; dependiendo de la inteligencia del middleware o del sistema operativo, los fallos pueden ser relativamente inofensivos como ilustra la figura.

El fuerte acoplamiento de una arquitectura como "1" hace que un fallo cualquiera sea fatal para la aplicación en su conjunto.

3.6. Complejidad de la Coordinación

La complejidad total no debe incrementarse considerablemente debido a los elementos que coordinan las interacciones entre componentes. Esto es similar al caso anterior de "complejidad lineal", pero consideraremos explícitamente el software que coordina la interacción (COORD), por ejemplo, el middleware, o el sistema operativo, etc.

C(total) = C(1) + C(2) + COORD

Lo que debemos buscar es:

COORD << C(total)

Es decir, la complejidad agregada por el coordinador debe ser muy pequeña con respecto a la complejidad del sistema en su conjunto.

Complejidad en coordinación

Como muestra la figura, la introducción de arquirecturas "3" y "4" necesariamente incrementa la complejidad de la solución, y se hace impráctica cuando los objetos que interactúan son muy sencillos. Es necesario considerar los retardos de ejecución y posiblemente el trabajo que involucra desarrollar (y diseñar') las interfaces de comunicación, así como el código que "serializa" y "des-serializa" los datos tal como se utilizan en el código.

4. Cuadro Resumen

La siguiente tabla resume el cruce de criterios y arquitecturas:

Criterio \\ Arquitectura 1 2 3 4

Accesibilidad

malo

regular

bueno

muy bueno

Integración Legacy

malo

malo

bueno

bueno

Complejidad no lineal

regular

regular

bueno

bueno

Rapidez de interacción

muy bueno

muy bueno

regular

malo

Resistencia a Fallos

malo

malo

bueno

muy bueno

Complejidad coordinación

bueno

bueno

malo

malo