La pregunta del millón es: “¿Qué es OSGi?” La respuesta más simple a esta pregunta es que se trata de una capa de modularidad para la plataforma Java. Por supuesto, la siguiente pregunta que podría surgir es: “¿Qué se entiende por modularidad?” Aquí usamos modularidad en el sentido tradicional, donde el código de la aplicación de software se divide en partes lógicas que representan intereses separados.
Aunque Java ofrece una serie adecuada de modificadores de acceso para controlar la visibilidad (por ejemplo, public
, protected
, private
y el acceso por paquete), estos tienden a abordar la encapsulación orientada a objetos a nivel bajo y no abordan realmente la partición lógica del sistema. Java cuenta con el concepto de paquete, que se utiliza típicamente para organizar el código. Para que el código sea visible desde un paquete Java hacia otro, debe declararse como public (o protected si se usa herencia). A veces, la estructura lógica de una aplicación requiere que cierto código pertenezca a diferentes paquetes, pero esto significa que cualquier dependencia entre los paquetes debe ser expuesta como public, lo cual lo hace accesible para cualquier otro. Con frecuencia, esto puede exponer detalles de la implementación, lo que dificulta la evolución futura, ya que otros usuarios pueden llegar a depender de su API que no está destinada a ser pública.
Considere el siguiente ejemplo:
// #1 package org.foo.hello; public interface Greeting { void sayHello(); }
// #2 package org.foo.hello.impl; import org.foo.hello.Greeting; public class GreetingImpl implements Greeting { final String m_name; public GreetingImpl(String name) { m_name = name; } public void sayHello() { System.out.println("Hello, " + m_name + "!"); } }
// #3 package org.foo.hello.main; importimportorg.foo.hello.Greeting; org.foo.hello.impl.GreetingImpl; public class Main { public static void main(String[] args) { Greeting greet = new GreetingImpl(“Hello World”); greet.sayHello(); } }
En el código anterior, el autor pudo haber tenido la intención de que un software tercerizado interactuara con la aplicación a través de la interfaz Greeting en (#1). Puede que haya mencionado esto en Javadoc, tutoriales, blogs o incluso en correos electrónicos, pero en realidad no hay nada que impida que una tercera parte construya una nueva instancia de GreetingImpl usando su constructor público en (#2), como se hace en (#3). Se podría argumentar que el constructor no debería ser público y que no es necesario dividir la aplicación en varios paquetes, lo cual podría ser cierto en este ejemplo trivial. Sin embargo, en aplicaciones del mundo real, la visibilidad a nivel de clase combinada con la organización en paquetes resulta ser una herramienta muy burda para asegurar la coherencia de la API. Al ver cómo una implementación supuestamente privada puede ser accedida por desarrolladores externos, ahora también se debe preocupar por los cambios en las firmas de las implementaciones privadas, además de las interfaces públicas, al realizar actualizaciones.
Este problema surge del hecho de que, aunque los paquetes de Java parecen tener una relación lógica mediante paquetes anidados, en realidad no la tienen. Un error común entre quienes aprenden Java por primera vez es suponer que la relación de paquete padre-hijo otorga privilegios de visibilidad especiales a los paquetes involucrados. Dos paquetes en una relación anidada son equivalentes a dos paquetes que no lo están. Los paquetes anidados son útiles principalmente para evitar conflictos de nombres y ofrecen solo un soporte parcial para la partición lógica del código.
El framework OSGi desempeña un papel central al crear aplicaciones basadas en OSGi, ya que constituye el entorno de ejecución de la aplicación. La especificación del framework de la OSGi Alliance define el comportamiento adecuado del framework, lo cual proporciona una API bien definida para programar. Esta especificación también permite la creación de múltiples implementaciones del núcleo del framework, lo que ofrece cierta libertad de elección; existen varios proyectos de código abierto reconocidos, como Apache Felix, Eclipse Equinox y Knopflerfish. Esto finalmente le beneficia, ya que no está atado a un proveedor en particular y puede programar de acuerdo con el comportamiento definido en la especificación.
La especificación OSGi, conceptualmente se divide en tres capas:
Al igual que en las arquitecturas en capas típicas, cada capa depende de las capas inferiores. Por lo tanto, es posible utilizar las capas inferiores de OSGi sin utilizar las capas superiores, pero no al revés. Los próximos tres capítulos discuten estas capas en detalle, pero aquí daremos una visión general de cada una.
La capa de módulo define el concepto de módulo OSGi, llamado bundle (paquete), que es simplemente un archivo JAR con metadatos adicionales (es decir, datos sobre los datos), como se muestra en la Figura. Un bundle contiene tus archivos de clase y sus recursos relacionados. Los bundles típicamente no son una aplicación completa empaquetada en un solo archivo JAR; en cambio, son los módulos lógicos que se combinan para formar una aplicación dada. Los bundles son más poderosos que los archivos JAR estándar, ya que puedes declarar explícitamente qué paquetes contenidos son visibles externamente (es decir, los paquetes exportados). En este sentido, los bundles extienden los modificadores de acceso normales (es decir, público, privado y protegido) asociados con el lenguaje Java.
Otra ventaja importante de los bundles sobre los archivos JAR estándar es que también puedes declarar explícitamente de qué paquetes externos depende tu bundle (es decir, los paquetes importados). El principal beneficio de declarar explícitamente los paquetes exportados e importados de tus bundles es que el marco OSGi puede gestionar y verificar la consistencia de tus bundles automáticamente; este proceso se llama resolución de bundles e involucra la coincidencia de paquetes exportados con paquetes importados. La resolución de bundles asegura la consistencia entre los bundles con respecto a versiones y otras restricciones.
La capa de ciclo de vida define cómo los bundles se instalan y gestionan dinámicamente en el marco OSGi. Si estuviéramos construyendo una casa, la capa de módulo proporcionaría los cimientos y la estructura, mientras que la capa de ciclo de vida sería el cableado eléctrico: hace que todo funcione.
La capa de ciclo de vida cumple dos propósitos diferentes. Externamente a tu aplicación, la capa de ciclo de vida define con precisión las operaciones del ciclo de vida del *bundle* (por ejemplo, instalar, actualizar, iniciar, detener y desinstalar). Estas operaciones del ciclo de vida permiten administrar, gestionar y hacer evolucionar tu aplicación de manera bien definida. Esto significa que los *bundles* pueden ser agregados y eliminados del marco de manera segura sin necesidad de reiniciar el proceso de la aplicación. Internamente a tu aplicación, la capa de ciclo de vida define cómo los *bundles* acceden a su contexto de ejecución, lo que les proporciona una forma de interactuar con el marco OSGi y las facilidades que este proporciona durante la ejecución. Este enfoque general de la capa de ciclo de vida es poderoso, ya que permite crear aplicaciones gestionadas externamente (y de forma remota) o aplicaciones completamente autogestionadas (o cualquier combinación de ambas).
La capa de servicios soporta y promueve un modelo de programación de aplicaciones flexible que incorpora conceptos popularizados por la computación orientada a servicios (aunque estos conceptos formaban parte del marco OSGi antes de que SOA se hiciera popular). Los conceptos principales giran en torno al patrón de interacción orientado a servicios de publicar, buscar y enlazar: los proveedores de servicios publican sus servicios en un registro de servicios, mientras que los clientes de servicios buscan en el registro para encontrar los servicios disponibles que desean usar (ver Figura anterior). Hoy en día, SOA está en gran medida asociado con los servicios web, pero los servicios OSGi son locales a una sola máquina virtual (VM), razón por la cual algunas personas lo denominan “SOA en una VM”.
La capa de servicios de OSGi es muy intuitiva, ya que promueve un enfoque de desarrollo basado en interfaces, lo cual generalmente se considera una buena práctica. Específicamente, promueve la separación entre interfaz e implementación. Los servicios OSGi son simplemente interfaces Java que representan un contrato conceptual entre los proveedores de servicios y los clientes de servicios. Esto hace que la capa de servicios sea muy ligera, ya que los proveedores de servicios son simplemente objetos Java a los que se accede mediante invocación directa de métodos. Además, la capa de servicios amplía el dinamismo basado en *bundles* de la capa de ciclo de vida con dinamismo basado en servicios, es decir, los servicios pueden aparecer o desaparecer en cualquier momento. El resultado es un modelo de programación que evita los enfoques monolíticos y frágiles del pasado, a favor de ser modular y flexible.
Como proyecto de ejemplo ver el siguiente repositorio: https://github.com/IS-LAB-EIC-UCN/osgi-tutorial