Líneas de comentario: Qué constituye la buena modularidad y por qué OSGi es sensacional

Modularidad es una de esas cosas que se consideran buenas — pero no siempre se realizan — en la ingeniería de software; difícil de describir y más difícil aún de lograr. Este artículo explora las consideraciones clave de la modularidad y luego las aplica a Java™, Maven y OSGi para ver qué tan bien se adecuan a esas posibilidades. Mostrará el valor que OSGi ofrece como sistema de modularidad para Java y proporcionará el contexto para explicar el valor de la aplicación de OSGi a aplicaciones empresariales. This content is part of the IBM WebSphere Developer Technical Journal.

Alasdair Nottingham, OSGi Applications Development lead, IBM

Author photoAlasdair Nottingham es el líder de desarrollo del dispositivo OSGi Applications de IBM de WebSphere Application Server. Trabaja en el laboratorio de desarrollo de IBM en Hursley, Reino Unido. Alasdair viene trabajando en el equipo de desarrollo de WebSphere Application Server desde el 2001 en mensajería, seguridad y tecnologías de OSGi. Alasdair es participante activo del cuerpo de estándares de OSGi Alliance, committer y asociado de PPMC de la incubadora de Apache Aries.



01-08-2011

Separando las partes del todo

En su nivel más básico, un "módulo" de software consiste en una interfaz y una implementación de la interfaz o más. La interfaz define las relaciones de esa clase con el mundo exterior.

Fuera del software, la modularización es muy común. El envío de mercancías es un gran ejemplo de eso. La introducción del contenedor intermodal de envío en los 1950 tuvo un impacto significativo en el costo del envío de mercancías alrededor del mundo. El contenedor tiene un tamaño estándar y un diseño estándar, que se apila en una forma estándar. Debido a esas características, fue posible proyectar buques, trenes y camiones para acomodar un contenedor de tamaño estándar sin tener en cuenta lo que iba adentro – trabajando principalmente sólo con la interfaz externa del contenedor. Ello redujo significativamente el tiempo necesario para cargar y descargar los buques, lo que posibilitó un uso más eficiente. El contenido del contenedor y la configuración del contenido eran responsabilidad de las personas que los llenaban.

En la TI también hay ejemplos excelentes de modularidad de hardware. Para actualizar la tarjeta gráfica de su computadora, usted tiene en cuenta la interfaz de la tarjeta madre (por ejemplo, PCI Express) y la entrada del monitor (VGA o DVI) y luego elige una tarjeta que soporta ambas interfaces. El monitor y la tarjeta madre no imponen restricciones a la tarjeta, como el chipset que usa para proporcionar el soporte; lo único que importa es la interfaz a la cual está conectada. Luego, el fabricante de tarjetas gráficas puede proyectar con seguridad el funcionamiento de la tarjeta (o cambiar su funcionamiento) y concentrar sus esfuerzos en la creación de una tarjeta gráfica de alta calidad sin preocuparse con lo que está al otro lado de la interfaz.

Sin embargo, en software, a menudo las cosas no se proyectan así — principalmente porque es difícil lograr una buena modularidad.


Consideraciones de modularidad

Vamos a comenzar estableciendo algunas de las consideraciones clave para la buena modularidad. Esas características son importantes porque contribuyen para hacer el código más independiente, flexible y resistente:

  • Contrato: ¿Cuál es el contrato entre este componente y sus clientes? Cómo acceden a él, cuál comportamiento se puede esperar, etc. En software, es común que los desarrolladores escriban código que depende de algún comportamiento no documentado de una función — por ejemplo, esperar la ocurrencia de un efecto secundario no documentado.
  • Visibilidad: Es probable que cualquier módulo tenga varias clases internas, métodos y utilidades útiles que existen para proporcionar la interfaz (o el contrato) del módulo, pero no son considerados por el módulo como algo externo. Frecuentemente, al usar un módulo proporcionado por un suministrador, puede resultar difícil distinguir lo que es interno y lo que es externo. El resultado es que, si se usa un módulo interno, la primera vez que el cliente lo descubre es cuando hay una anomalía de compilación o cuando ocurre una anomalía de tiempo de ejecución porque la firma del método cambió.
  • Coexistencia: Cualquier sistema puede consistir en un conjunto de módulos, donde cada módulo depende de otros módulos. Esos módulos deben tener la capacidad de ejecutar juntos sin causar problemas para los otros módulos. Como un ejemplo simple de eso, el JDK contiene dos clases List; la primera forma parte de AWT (Abstract Window Toolkit) y la segunda, de la API Java Collections. Una clase no interfiere en la otra porque están en paquetes distintos. Aunque los paquetes tomen algunas medidas para permitir la coexistencia, es posible que ocurran problemas ocasionales — el más grande de ellos es el mantenimiento de versiones de las clases.
  • Sustituibilidad: Tras definir un contrato o interfaz, hay que tener la capacidad de sustituir el módulo sin estropear el sistema. Piense en el ejemplo anterior sobre la tarjeta gráfica: es posible que sustituya (o actualice) su tarjeta gráfica sin preocuparse por estropear todo el sistema.

Aplicando las consideraciones de modularidad

Contenedores de envío

Para validar que las consideraciones de modularidad arriba son razonables, vamos a aplicarlas al ejemplo del contenedor de envío y ver si se mantienen válidas:

  • Contrato: Un contenedor tiene tamaño estándar y tiene pies que pueden encajarse en la parte superior de otro contenedor. Eso permite el apilado de los contenedores unos sobre los otros. Buques, camiones, grúas y trenes construidos para acomodar esos estándares pueden aceptar contenedores sin saber quién lo construyó o lo que hay adentro.
  • Visibilidad: Una grúa, un buque o camión construido para aceptar un contenedor no se importa por lo que hay dentro de cada uno, y el contenido de cada contenedor es irrelevante para todos los demás. Si usted tiene un contenedor lleno de fresas, no necesita preocuparse por poner un contenedor lleno de autos sobre él (aunque quizás eso sea una mala idea por otros motivos, eso no es efectivamente una consideración de modularidad).
  • Coexistencia: Un contenedor no necesita saber las características de otros contenedores. No importa si tienen una pintura diferente o fabricantes diferentes.
  • Sustituibilidad: Un contenedor puede ser intercambiado fácilmente y sustituido sin afectar otros contenedores o el transportador.

Como se puede ver, los contenedores de envío se adecuan muy bien a esas consideraciones de modularidad.

Java

Si aplicamos esas consideraciones a Java, se puede ver rápidamente que Java por si sólo no proporciona el mejor entorno para la modularidad:

  • Contrato: Aunque Javadoc proporciona una forma excelente de documentar la API, el valor predeterminado sólo describe las clases, interfaces y métodos. Sin embargo, para obtener el verdadero beneficio del contrato, usted necesita que el desarrollador escriba buenos comentarios de Javadoc. Muy frecuentemente, las APIs acaban teniendo una documentación insuficiente o incorrecta (si existe), que no se compara a los recursos de relleno automático de los IDEs modernos. Para ser justo, éste no es un problema de Java; el mantenimiento de buenas prácticas de desarrollo es responsabilidad del equipo del proyecto.
  • Visibilidad: Java tiene dos esquemas principales de visibilidad. El primero es un marcador de visibilidad que marca campos, métodos y clases para visibilidad pública, protegida, pública o predeterminada. Ese marcador es impuesto fuertemente por el compilador y débilmente en el tiempo de ejecución; es decir, el tiempo de ejecución expondrá las partes internas a través de reflexión si se le solicita. El segundo es que todo lo que está en la ruta de acceso de clases es accesible; eso significa que las clases que forman la parte interna son visibles para el interlocutor.
  • Coexistencia: Observando nuevamente la ruta de acceso de clases, no es posible que dos módulos cuenten con una biblioteca común cuando esos módulos están en versiones diferentes que no tienen compatibilidad binaria. Usted tendría que actualizar todo en sincronía y, aunque eso pueda ser fácil en una aplicación simple, puede involucrar pruebas y algunos retos de QA en una aplicación compleja.
  • Sustituibilidad: Existen dos aspectos de la Sustituibilidad: en frío (se permite el reinicio de la JVM) y en caliente (sin reinicio de la JVM). La Sustituibilidad en frío es factible, pero se necesita saber cuáles archivos JAR de la ruta de acceso de clases deben ser eliminados y sustituidos. Eso puede parecer simple, pero en realidad, hacerlo correctamente puede ser muy complejo. Java no proporciona Sustituibilidad en caliente.

Aunque Java por si solo no cumple con esos requisitos, sí proporciona herramientas para posibilitar que la modularidad se base en él. Cualquier persona que use un servidor de aplicaciones Java EE sabe que esas consideraciones de modularidad pueden ser obtenidas en Java: cada archivo EAR está separado de los demás y es posible tener varios EARs ejecutando con niveles de código diferentes. Sin embargo, el punto de vista de Java EE es que cada uno de esos EARs es una aplicación independiente — y no un módulo; por tanto, aunque tenga algunos comportamientos de modularidad, los tiene con baja granularidad.

Así, considerando que Java por si solo no proporciona una buena solución de modularidad, ¿qué opciones tiene usted? Existen dos sistemas complementares que se usan con frecuencia: gestión de dependencia y sistemas de módulo.


Soluciones de modularidad

Gestión de dependencia

Generalmente, el primer paso para adoptar la modularidad es usar un sistema de gestión de dependencia de tiempo de compilación. Uno de los más conocidos es Maven, en el cual usted define el módulo con un nombre y una versión y define el contenido al colocar el origen Java en él. Luego, otros módulos pueden depender de su módulo de acuerdo con el nombre y la versión. Maven también facilita la definición de módulos que agregan otros módulos. La agregación permite tener un módulo de API y un módulo de implementación; los módulos que dependen del suyo compilarían de acuerdo con el módulo de API y ejecutarían de acuerdo con el agregado de la API e implementación.

Los sistemas de gestión de dependencia como, Maven y otros, definen las dependencias entre los módulos. Frecuentemente, eso proporciona modularidad suficiente para muchas personas, pero compare esas provisiones a las consideraciones de modularidad a ver cuánto valor se proporciona efectivamente:

  • Contrato: Un sistema de gestión de dependencia define la identidad del módulo como parte del contrato. Sin embargo, se trata de una construcción artificial y limita mucho lo que el proveedor puede hacer. Por ejemplo, si usted refactoriza un módulo en dos, estropea los clientes que dependen de que sea uno; obtendrán parte de la API necesaria, pero no toda la API. El código propiamente dicho no cambia, pero los metadatos del módulo necesitan cambiar. Existen maneras de superar eso — usted puede producir módulos agregados — pero ésas son soluciones alternas en el mejor de los casos y exponen más que lo necesario. Volviendo a la analogía de la tarjeta gráfica, eso significaría que dicha tarjeta podría ver no sólo PCI Express — que es lo que necesita — sino también las interfaces de la tarjeta madre para la CPU, impresora, RAM, etc. — que no necesita.
  • Visibilidad: Al usar la gestión de dependencia, se trata de la visibilidad en el tiempo de compilación, no en el tiempo de ejecución. Eso puede ser suficiente, pero tener acceso a la parte interna es sencillamente una cuestión de añadir una dependencia al módulo de implementación. El usuario, no el proveedor, tiene el control sobre la visibilidad de un módulo; no hay imposición en el tiempo de ejecución porque la visibilidad normal de Java entra en vigor en el tempo de ejecución.
  • La coexistencia no es soportada en el tiempo de ejecución — allí se usa la carga normal de clases de Java — pero es posible que dos módulos compilen de acuerdo con versiones diferentes de una dependencia sin problemas.
  • La Sustituibilidad está limitada al cambio de la dependencia para que dependa de otra cosa. Sin embargo, generalmente eso sólo ocurre cuando se realiza una recopilación.

Aunque la gestión de dependencia comience como una buena solución de modularidad, va sólo hasta la mitad del camino.

OSGi

Entre los varios intentos diferentes de proyectar un sistema de módulos para Java, el más conocido es OSGi. OSGi define un módulo (llamado paquete) que declara dependencias de los paquetes de los que necesita los paquetes que proporciona. El concepto de ciclo de vida del módulo y el modelo de servicio se basan en ese concepto. El modelo de servicio usa un registro de servicio donde es posible anunciar los objetos a través de la interfaz que proporcionan. El registro de servicio es dinámico — por tanto, los servicios vienen y van. Los usuarios de los servicios pueden ser notificados cuando se elimina un servicio y pueden pasar a usar otro servicio.

Así que, ¿cómo OSGi se compuerta respecto a las consideraciones modulares?

  • Contrato: OSGi proporciona un buen equivalente al contrato. Un módulo declara las posibilidades que proporciona a los módulos clientes y las posibilidades que necesita de otros módulos en términos de paquetes. Usted no declara dependencias en los módulos, sino en los paquetes que usa. Luego, el sistema de modularidad puede establecer la coincidencia entre usted y lo que usted necesita, no importando el módulo que lo proporciona. El sistema de modularidad no expondrá le expondrá a cosas que usted no declaró como requisito.
  • Visibilidad: OSGi tiene dos formas de asegurar la visibilidad. Primero, los módulos sólo ven los paquetes de los cuales declararon una dependencia. Segundo, un módulo declara los paquetes que expone — por tanto, los paquetes que no se exponen son privados. Eso significa que queda muy claro cuáles paquetes son privados y OSGi impone eso.
  • La coexistencia es soportada con el mantenimiento de versiones de los paquetes. Cada módulo puede definir las versiones de los paquetes que proporciona y necesita. Eso permite que dos módulos que usan versiones diferentes de un paquete existan en el sistema.
  • Sustituibilidad: OSGi permite que un módulo sea apagado y sustituido en el tiempo de ejecución. El efecto que eso tiene en el sistema varía de acuerdo con la naturaleza del cambio y la forma como la aplicación está escrita. Algunos cambios producirán una perturbación significativa; otros son menos importantes. Las actualizaciones que cambian la vista de la aplicación respecto a las clases que tiene producen más perturbación que la sustitución de un servicio registrado en el registro de servicios.

Como se puede ver, OSGi se adecua bien a las consideraciones de modularidad.


Conclusión

Tras revisar las consideraciones críticas para la buena modularidad en Java, queda claro que OSGi quizás se adecue mejor. La primera oleada de modularización en Java comenzó con grandes productos de software, como IDEs, seguidos por los servidores de aplicaciones. Las consideraciones presentadas en este artículo figuran entre las razones por las cuales se seleccionó OSGi como el mecanismo para modularizar IBM® WebSphere® Application Server. Además, varios proyectos grandes de Java, como Eclipse, Glassfish, Apache Geronimo 3 y Apache ServiceMix se basan en OSGi.

La próxima oleada de adopción será la de aplicaciones grandes que también podrían beneficiarse de las mismas posibilidades de modularidad que los servidores de aplicaciones que las ejecutan. Fue a partir de esa percepción que se formó el grupo OSGi Enterprise Expert para asegurar que las aplicaciones empresariales Java pudiesen aprovechar los beneficios de OSGi. La OSGi Alliance lanzó la primera versión de la OSGi Service Platform Enterprise Specification (versión 4.2), en marzo del 2010, e IBM lanzó el WebSphere Application Server Feature Pack para Aplicaciones OSGi y JPA 2.0 posteriormente en el mismo año.

Recursos

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=WebSphere
ArticleID=746583
ArticleTitle=Líneas de comentario: Qué constituye la buena modularidad y por qué OSGi es sensacional
publish-date=08012011