Modularidad en Java 9: ​​apilamiento con Project Jigsaw, Penrose y OSGi

Este artículo proporciona una descripción general de propuestas, especificaciones y plataformas destinadas a hacer que la tecnología Java sea más modular en Java 9. Discutiré los factores que contribuyen a la necesidad de una arquitectura Java más modular, describiré brevemente y compararé las soluciones que se han propuesto. e introducir las tres actualizaciones de modularidad previstas para Java 9, incluido su impacto potencial en el desarrollo de Java.

¿Por qué necesitamos la modularidad de Java?

La modularidad es un concepto general. En software, se aplica a la escritura e implementación de un programa o sistema informático como una serie de módulos únicos, en lugar de como un diseño único y monolítico. Luego se utiliza una interfaz estandarizada para permitir que los módulos se comuniquen. La división de un entorno de construcciones de software en distintos módulos nos ayuda a minimizar el acoplamiento, optimizar el desarrollo de aplicaciones y reducir la complejidad del sistema.

La modularidad permite a los programadores realizar pruebas de funcionalidad de forma aislada y participar en esfuerzos de desarrollo paralelos durante un sprint o proyecto determinado. Esto aumenta la eficiencia durante todo el ciclo de vida del desarrollo de software.

Algunos atributos característicos de un módulo genuino son:

  • Una unidad autónoma de despliegue (acoplamiento flexible)
  • Una identidad única y consistente (ID de módulo y versión)
  • Requisitos y dependencias fácilmente identificados y descubiertos (tiempo de compilación estándar e instalaciones de implementación y metainformación)
  • Una interfaz abierta y comprensible (contrato de comunicación)
  • Detalles de implementación ocultos (encapsulación)

Los sistemas que están diseñados para procesar módulos de manera eficiente deben hacer lo siguiente:

  • Admite la modularidad y el descubrimiento de dependencias en tiempo de compilación
  • Ejecute módulos en un entorno de ejecución que admita una implementación y redespliegue sencillas sin tiempo de inactividad del sistema
  • Implementar un ciclo de vida de ejecución que sea claro y sólido
  • Proporcionar facilidades para el registro y descubrimiento de módulos fáciles

Las soluciones orientadas a objetos, a componentes y a servicios han intentado permitir la modularidad pura. Sin embargo, cada solución tiene su propio conjunto de peculiaridades que le impiden alcanzar la perfección modular. Consideremos brevemente.

Clases y objetos Java como construcciones modulares

¿No satisface la naturaleza orientada a objetos de Java los requisitos de modularidad? Después de todo, la programación orientada a objetos con Java enfatiza y, a veces, impone la singularidad, la encapsulación de datos y el acoplamiento flexible. Si bien estos puntos son un buen comienzo, observe los requisitos de modularidad que no cumple el marco orientado a objetos de Java: la identidad a nivel de objeto no es confiable; las interfaces no están versionadas: y las clases no son únicas en el nivel de implementación. El acoplamiento flojo es una buena práctica, pero ciertamente no se aplica.

Reutilizar clases en Java es difícil cuando las dependencias de terceros se usan mal con tanta facilidad. Las herramientas de tiempo de compilación como Maven buscan abordar esta deficiencia. Las convenciones y construcciones de lenguaje posteriores a los hechos, como la inyección de dependencia y la inversión de control, ayudan a los desarrolladores en nuestros intentos de controlar el entorno de ejecución y, a veces, tienen éxito, especialmente si se usan con disciplina estricta. Desafortunadamente, esta situación deja la tarea de crear un entorno modular a las convenciones y configuraciones del marco propietario.

Java también agrega espacios de nombres de paquetes y visibilidad de alcance a la combinación como un medio para crear mecanismos modulares de tiempo de compilación y tiempo de implementación. Pero estas características del lenguaje se eluden fácilmente, como explicaré.

Paquetes como solución modular

Los paquetes intentan agregar un nivel de abstracción al panorama de programación de Java. Proporcionan funciones para espacios de nombres de codificación y contextos de configuración únicos. Lamentablemente, sin embargo, las convenciones de los paquetes se eluden fácilmente, lo que con frecuencia conduce a un entorno de acoplamientos peligrosos en tiempo de compilación.

El estado de modularidad en Java en la actualidad (aparte de OSGi, que discutiré en breve) se logra con mayor frecuencia utilizando espacios de nombres de paquetes, convenciones de JavaBeans y configuraciones de marcos de propiedad como las que se encuentran en Spring.

¿No son los archivos JAR lo suficientemente modulares?

Los archivos JAR y el entorno de implementación en el que operan mejoran en gran medida las muchas convenciones de implementación heredadas disponibles de otro modo. Pero los archivos JAR no tienen una singularidad intrínseca, aparte de un número de versión que se usa con poca frecuencia, que está oculto en un manifiesto .jar. El archivo JAR y el manifiesto opcional no se utilizan como convenciones de modularidad dentro del entorno de ejecución de Java. Por lo tanto, los nombres de paquetes de clases en el archivo y su participación en una ruta de clases son las únicas partes de la estructura JAR que prestan modularidad al entorno de ejecución.

En resumen, los JAR son un buen intento de modularización, pero no cumplen con todos los requisitos para un entorno verdaderamente modular. Los marcos y plataformas como Spring y OSGi usan patrones y mejoras a la especificación JAR para proporcionar entornos para construir sistemas modulares y muy capaces. Sin embargo, con el tiempo, incluso estas herramientas sucumbirán a un efecto secundario muy desafortunado de la especificación JAR. ¡JAR hell!

Classpath / JAR infierno

Cuando el entorno de ejecución de Java permite mecanismos de carga JAR arbitrariamente complejos, los desarrolladores saben que están en el infierno de classpath o en el infierno de JAR . Varias configuraciones pueden provocar esta condición.

Primero, considere una situación en la que un desarrollador de aplicaciones Java proporciona una versión actualizada de la aplicación y la ha empaquetado en un archivo JAR con el mismo nombre exacto que la versión anterior. El entorno de ejecución de Java no proporciona funciones de validación para determinar el archivo JAR correcto. El entorno de ejecución simplemente cargará las clases del archivo JAR que encuentre primero o que satisfaga una de las muchas reglas de classpath. Esto conduce a un comportamiento inesperado en el mejor de los casos.

Otro ejemplo del infierno de JAR surge cuando dos o más aplicaciones o procesos dependen de diferentes versiones de una biblioteca de terceros. Al utilizar las funciones estándar de carga de clases, solo una versión de la biblioteca de terceros estará disponible en tiempo de ejecución, lo que provocará errores en al menos una aplicación o proceso.

Un sistema de módulos Java eficiente y con todas las funciones debería facilitar la separación del código en módulos distintos, fáciles de entender y poco acoplados. Las dependencias deben especificarse claramente y hacerse cumplir estrictamente. Deben estar disponibles instalaciones que permitan actualizar los módulos sin tener un efecto negativo en otros módulos. Un entorno de ejecución modular debe permitir configuraciones que sean específicas de un dominio particular o mercado vertical, reduciendo así el tiempo de inicio y la huella del sistema del entorno.

Soluciones de modularidad para Java

Junto con las características de modularidad mencionadas hasta ahora, los esfuerzos recientes agregan algunas más. Las siguientes funciones están destinadas a optimizar el rendimiento y permitir la ampliación del entorno de ejecución:

  • Código fuente segmentado : código fuente separado en distintos segmentos en caché, cada uno de los cuales contiene un tipo específico de código compilado. Los objetivos incluyen omitir el código que no es de método durante los barridos de basura, las compilaciones incrementales y una mejor gestión de la memoria.
  • Ejecuciones en tiempo de compilación : construcciones de lenguaje para hacer cumplir los espacios de nombres, el control de versiones, las dependencias y otros.
  • Instalaciones de implementación : soporte para implementar entornos de tiempo de ejecución escalados de acuerdo con necesidades específicas, como las de un entorno de dispositivo móvil.

Varias especificaciones y marcos de modularidad han intentado facilitar estas características, y algunas se han elevado recientemente a la cima en propuestas para Java 9. A continuación se ofrece una descripción general de las propuestas de modularidad de Java.

JSR (solicitud de especificación de Java) 277

Actualmente inactivo está Java Specification Request (JSR) 277, el Java Module System; introducido por Sun en junio de 2005. Esta especificación cubría la mayoría de las mismas áreas que OSGi. Al igual que OSGi, JSR 277 también define el descubrimiento, la carga y la coherencia de los módulos, con escaso soporte para modificaciones en tiempo de ejecución y / o verificación de integridad.

Los inconvenientes de JSR 277 incluyen:

  • Sin carga y descarga dinámicas de módulos / paquetes
  • Sin comprobaciones de tiempo de ejecución para la unicidad del espacio de clase

OSGi (Iniciativa de puerta de enlace de servicio abierto)

Introducida por la OSGI Alliance en noviembre de 1998, la plataforma OSGI es la respuesta de modularidad más utilizada a la pregunta estándar formal para Java. Actualmente en la versión 6, la especificación OSGi es ampliamente aceptada y utilizada, especialmente en los últimos tiempos.

En esencia, OSGi es un sistema modular y una plataforma de servicios para el lenguaje de programación Java que implementa un modelo de componentes completo y dinámico en forma de módulos, servicios, paquetes desplegables, etc.

Las capas principales de la arquitectura OSGI son las siguientes:

  • Entorno de ejecución : el entorno Java (por ejemplo, Java EE o Java SE) en el que se ejecutará un paquete.
  • Módulo : donde el marco OSGi procesa los aspectos modulares de un paquete. Los metadatos del paquete se procesan aquí.
  • Ciclo de vida : aquí se inicia, inicia y detiene los paquetes.
  • Registro de servicios : donde los paquetes enumeran sus servicios para que otros paquetes los descubran.

Uno de los mayores inconvenientes de OSGi es su falta de un mecanismo formal para la instalación de paquetes nativos.

JSR 291

JSR 291 es un marco de componentes dinámicos para Java SE que se basa en OSGi, actualmente se encuentra en la etapa final de desarrollo. Este esfuerzo se centra en llevar OSGi a Java convencional, como lo hizo JSR 232 para el entorno móvil Java.

JSR 294

JSR 294 define un sistema de metamódulos y delega la realización real de los módulos conectables (versiones, dependencias, restricciones, etc.) a proveedores externos. Esta especificación introduce extensiones de lenguaje, como "superpaquetes" y módulos relacionados jerárquicamente, para facilitar la modularidad. La encapsulación estricta y las distintas unidades de compilación también son parte del enfoque de la especificación. JSR 294 está actualmente inactivo.

Proyecto Jigsaw

Project Jigsaw es el candidato más probable para la modularidad en Java 9. Jigsaw busca utilizar construcciones de lenguaje y configuraciones de entorno para definir un sistema de módulos escalables para Java SE. Los objetivos principales de Jigsaw incluyen:

  • Haciendo muy fácil escalar el tiempo de ejecución de Java SE y el JDK a dispositivos pequeños.
  • Mejorar la seguridad de Java SE y JDK al prohibir el acceso a las API internas de JDK y al hacer cumplir y mejorar el SecurityManager.checkPackageAccessmétodo.
  • Mejorar el rendimiento de la aplicación mediante optimizaciones del código existente y facilitar técnicas de optimización de programas anticipados.
  • Simplificar el desarrollo de aplicaciones dentro de Java SE al permitir que las bibliotecas y aplicaciones se construyan a partir de módulos aportados por el desarrollador y desde un JDK modular.
  • Requerir y hacer cumplir un conjunto finito de restricciones de versión

JEP (propuesta de mejora de Java) 200

La propuesta de mejora de Java 200 creada en julio de 2014, busca definir una estructura modular para el JDK. JEP 200 se basa en el marco Jigsaw para facilitar la segmentación del JDK, de acuerdo con Java 8 Compact Profiles, en conjuntos de módulos que se pueden combinar en tiempo de compilación, tiempo de construcción y tiempo de implementación. Estas combinaciones de módulos se pueden implementar como entornos de tiempo de ejecución reducidos que se componen de módulos compatibles con Jigsaw.

JEP 201

JEP 201 busca basarse en Jigsaw para reorganizar el código fuente de JDK en módulos. Estos módulos pueden luego compilarse como unidades distintas mediante un sistema de construcción mejorado que refuerza los límites de los módulos. JEP 201 propone un esquema de reestructuración del código fuente en todo el JDK que enfatiza los límites del módulo en el nivel superior de los árboles de código fuente.

Penrose

Penrose administraría la interoperabilidad entre Jigsaw y OSGi. Específicamente, facilitaría la capacidad de modificar los micro-kernels OSGi para que los paquetes que se ejecutan en el kernel modificado utilicen módulos Jigsaw. Se basa en el uso de JSON para describir módulos.

Planes para Java 9

Java 9 es una versión principal única para Java. Lo que lo hace único es la introducción de componentes y segmentos modulares en todo el JDK . Las características principales que respaldan la modularización son:

  • Código fuente modular : en Java 9, JRE y JDK se reorganizarán en módulos interoperables. Esto permitirá la creación de tiempos de ejecución escalables que se pueden ejecutar en dispositivos pequeños.
  • Caché de código segmentado : aunque no es estrictamente una instalación modular, el nuevo caché de código segmentado de Java 9 seguirá el espíritu de la modularización y disfrutará de algunos de los mismos beneficios. La nueva caché de código tomará decisiones inteligentes para compilar segmentos de código a los que se accede con frecuencia en código nativo y almacenarlos para una búsqueda optimizada y una ejecución futura. El montón también se segmentará en 3 unidades distintas: código sin método que se almacenará permanentemente en la caché; código que tiene un ciclo de vida potencialmente largo (conocido como "código sin perfil"); y código que es transitorio (conocido como "código perfilado").
  • Cumplimiento del tiempo de construcción : el sistema de construcción se mejorará, a través de JEP 201, para compilar y hacer cumplir los límites del módulo.
  • Instalaciones de implementación : las herramientas se proporcionarán dentro del proyecto Jigsaw que admitirán los límites, las restricciones y las dependencias de los módulos en el momento de la implementación.

Versión de acceso anticipado de Java 9

Si bien la fecha exacta de lanzamiento de Java 9 sigue siendo un misterio, puede descargar una versión de acceso anticipado en Java.net.

En conclusión

Este artículo ha sido una descripción general de la modularidad dentro de la plataforma Java, incluidas las perspectivas de modularidad en Java 9. Expliqué cómo los problemas de larga data como el infierno de classpath contribuyen a la necesidad de una arquitectura Java más modular y discutí algunas de las nuevas modularidades más recientes. características propuestas para Java. Luego describí y contextualicé cada una de las propuestas o plataformas de modularidad de Java, incluidos OSGi y Project Jigsaw.

La necesidad de una arquitectura Java más modular es clara. Los intentos actuales se han quedado cortos, aunque OSGi se acerca mucho. Para el lanzamiento de Java 9, Project Jigsaw y OSGi serán los jugadores principales en el espacio modular para Java, con Penrose posiblemente proporcionando el pegamento entre ellos.

Esta historia, "Modularidad en Java 9: ​​apilarse con Project Jigsaw, Penrose y OSGi" fue publicada originalmente por JavaWorld.