Plugin Architecture

Qué organiza

Plugin Architecture permite extender un host mediante módulos que contribuyen a contratos o extension points definidos. Este canonical trata el caso arquitectónicamente significativo: las contribuciones afectan extensibilidad, fronteras, compatibilidad y evolución del sistema. Un callback local puede ser una decisión de diseño; instalar plugins en un framework no demuestra que la aplicación completa tenga esta arquitectura.

El modelo de host y extender de Eclipse ofrece una descripción concreta y reconocida de esos papeles y sus colaboraciones. Notes on the Eclipse Plug-in Architecture. Sus mecanismos históricos ilustran el modelo; no son una plantilla universal de XML, clases o directorios.

Host, plugins y puntos de extensión

En un ejemplo propio, una plataforma de revisión ofrece un punto para validadores y otro para exportadores:

Host
├── Validation extension point
│   ├── Format validator
│   └── Accessibility checker
└── Export extension point
    └── Report exporter

El host decide cuándo requiere una contribución y cómo incorpora su resultado. El plugin aporta una capacidad bajo ese contrato. Un extension point puede admitir una única implementación, varias acumulables o una selección por contexto: son decisiones que deben explicitarse.

Una funcionalidad seleccionable no queda autorizada a redefinir reglas centrales. En nuestro ejemplo, un exportador no debería cambiar quién aprobó la revisión. La API expuesta al plugin puede ofrecer una operación acotada en lugar de entregar acceso irrestricto al estado del host.

Discovery no es activation

MomentoPregunta que resuelve
Discovery¿Qué extensiones candidatas existen?
Registration¿Qué contribuyen y con qué requisitos declarados?
Loading¿Cómo hacemos disponible su código y dependencias?
Activation¿Cuándo se preparan para prestar su capacidad?
Deactivation¿Cómo dejan de aceptar trabajo y liberan recursos?

Esta separación permite detectar una contribución sin iniciar sus conexiones o tareas. Por ejemplo, VS Code documenta carga lazy basada en activation events dentro de su Extension Host.

El modelo no obliga a discovery automático, descarga remota o hot reload. Un registro explícito durante startup también puede conectar extensiones. La simplicidad de la selección no elimina el lifecycle: una extensión que abre recursos necesita una política de cierre.

En nuestro ejemplo, activation puede fallar porque falta una dependencia. El host debe decidir si continúa sin esa capacidad o impide el flujo que la exige. Marcar toda ausencia como «plugin opcional» puede publicar resultados incompletos sin advertirlo.

Contratos y dependency direction

Una variante define la API de extensión en un paquete controlado por el host. Plugins dependen de ese contrato y el host los invoca mediante sus abstracciones. El host que importa cada implementación para conocer su comportamiento conserva acoplamiento a todas sus extensiones.

Plugins también pueden requerir otros plugins o capacidades. El host necesita resolver versiones, incompatibilidades y disponibilidad antes de activar una combinación. Dependencias cíclicas o referencias directas a detalles de otro plugin dificultan reemplazo, arranque y rollback.

En nuestro ejemplo, un exportador puede requerir la capacidad de producir observaciones normalizadas, no conocer una clase privada del validador. Aun así, convertir toda interacción en una API genérica puede ocultar dependencias esenciales. Conviene conservar contratos que expresen propósito y obligaciones.

El orden también forma parte del contrato: acumulación de observaciones independientes no es lo mismo que una secuencia donde un plugin modifica la entrada del siguiente. Deben definirse conflictos entre contribuciones y autoridad sobre resultados.

API evolution y compatibilidad

La documentación de Eclipse distingue compatibilidad contractual y binaria en Evolving Java-based APIs. Compilar sin errores y mantener firmas no basta para conservar comportamiento.

Según lenguaje y runtime, source compatibility permite volver a compilar código existente; binary compatibility permite cargar y ejecutar artefactos previamente compilados sin recompilarlos. Sus reglas concretas varían. La compatibilidad semántica también exige mantener obligaciones sobre resultados, errores, concurrencia y lifecycle.

En nuestro ejemplo, el host agrega un resultado desconocido a una evaluación que antes solo producía aprobado/rechazado. Un plugin puede seguir cargando y asumir que todo resultado diferente de rechazado autoriza publicación. El cambio rompió una expectativa relevante sin alterar necesariamente la carga binaria.

La literatura de OSGi sobre evolución independiente subraya que los roles que consumen una API y los que la implementan pueden tener necesidades distintas de compatibilidad. No trasladar una regla de versiones de un runtime a todos los lenguajes.

Version y capability negotiation

Una versión de contrato delimita obligaciones conocidas. Un rango declarado permite al host rechazar combinaciones no admitidas; una negociación explícita puede seleccionar una versión común. No todos los sistemas requieren un protocolo de negociación en runtime: el registro y el despliegue pueden resolverlo antes.

Las capabilities expresan funciones disponibles, como exportar cierto formato. Su presencia no equivale a una versión ni a un permiso. Un plugin compatible puede carecer de una capability opcional; tener la capability no autoriza acceder a todos los documentos.

Lifecycle compatibility incluye cuándo se invocan callbacks, si pueden ejecutarse concurrentemente y qué significa detener una extensión. La política debe permitir transiciones y diagnóstico, con pruebas de contratos viejos cuando se declara backward compatibility.

Permisos, recursos y aislamiento

La separación lógica no es aislamiento de proceso o seguridad. Un plugin in-process puede bloquear threads, agotar memoria, lanzar excepciones o leer recursos disponibles a la identidad del host. Contextos de carga separados pueden resolver dependencias sin crear un sandbox, como advierte la guía de plugins de .NET.

En nuestro ejemplo, declarar «solo exportación» en un manifiesto no impide acceso directo a archivos si el runtime lo permite. El host puede mediar operaciones y limitar autoridad; las restricciones requieren mecanismos efectivos para el código ejecutado.

Un proceso separado puede contener ciertos fallos de runtime. No basta si conserva credenciales amplias, comparte recursos ilimitados o todos los plugins dependen de un mismo servicio saturado. blast-radius y bulkhead ayudan a identificar alcance de fallos y recursos protegidos. El costo incluye canales, supervisión, versiones y manejo de resultados inciertos.

Relación con Microkernel y otros alcances

microkernel centra el diseño en un core estable ampliado por extensiones. Plugin Architecture enfatiza el mecanismo estructural host→extension points→contribuciones. Existe fuerte superposición y algunas fuentes usan ambos nombres para aproximaciones similares; no se propone una separación terminológica universal.

Una aplicación de varias capas puede tener plugins solo en su subsistema de reportes. Eso no obliga a describir toda su arquitectura como Microkernel. También puede existir dentro de un modular-monolith, preservando contratos y ownership de sus módulos.

architectural-pattern ayuda a preguntar qué fuerzas resuelve el mecanismo; design-pattern permite reconocer colaboraciones locales. architecture-profile recuerda que elegir plugins no decide comunicación, datos o deployment del sistema completo.

La revisión debe comprobar extensiones reales, contratos estables, compatibilidad, lifecycle y límites de autoridad. Si los plugins cambian siempre junto al host o su API expone todos sus detalles internos, la extensibilidad prometida puede ser menor que su costo.