Be Software Architecture Wise
Be Wise Apps

Read in English · Lire en français · Auf Deutsch lesen

Sección 5.5 de 5.8 · Intermedio · 24 min de lectura

Arquitecturas de microkernel y de plug-ins

Videolección

4 min · narrado en inglés, con subtítulos
Solo escuchar (audio)
Leer la transcripción

Muchos productos de éxito son un núcleo pequeño que otros amplían. En esta lección aprenderás cómo funciona el estilo microkernel, o de plug-ins, dónde encaja y cómo mantener sanos sus contratos a lo largo de los años.

Piensa en una consola de videojuegos. La consola en sí hace unas pocas cosas generales: arranca, muestra un menú, lee los mandos y dibuja imágenes. Cada juego es un cartucho que se inserta mediante un conector estándar. Puedes añadir juegos nuevos durante años sin abrir la consola, y un juego mal hecho no puede volver a cablearla. Una arquitectura de microkernel construye el software de la misma manera.

Esta es la forma. El sistema central contiene la lógica mínima y general: el flujo de trabajo principal y los puntos donde se acoplan los plug-ins. En el proceso de pago de una plataforma de reparto de comida, eso es poner precio a un carrito, cobrar el pago y confirmar el pedido. Dentro del núcleo, un registro anota qué plug-ins existen, sus versiones y el contrato que implementa cada uno. Permite al núcleo pedir las normas fiscales de Alemania sin saber qué clase las proporciona. Los plug-ins se conectan solo mediante contratos que publica el núcleo. A un lado, las normas fiscales del Reino Unido, Alemania y Estados Unidos. Al otro, los métodos de pago: tarjetas, billeteras digitales y pago al recibir. Dos reglas lo mantienen todo unido. Los plug-ins nunca se llaman entre sí, y el núcleo nunca nombra un plug-in concreto.

El estilo se adapta a productos con un flujo de trabajo estable pero muchas variantes de pasos concretos. Normas fiscales y de nóminas por país, normas de seguros por jurisdicción, métodos de pago, importadores de archivos. También se adapta a plataformas que quieren un ecosistema de extensiones de terceros. Las herramientas de desarrollo como Eclipse y Visual Studio Code, y los navegadores web, están construidos así. Visual Studio Code incluso ejecuta las extensiones en un proceso separado, para que una que se comporte mal no pueda congelar el editor.

Diseñemos una. La plataforma de reparto de comida se lanza en Alemania y Estados Unidos después del Reino Unido, y su proceso de pago ya está lleno de ramas para cada país. Primero, nombra el punto de variación. Solo el impuesto sobre un carrito con precio varía según el país. Los precios, el pago y los recibos se quedan en el núcleo, lo que mantiene pequeño el contrato. Después, decide qué cruza el contrato. Entran las líneas del carrito, cada una con una categoría neutral, como comida caliente o tarifa de envío. Salen el impuesto total y un desglose etiquetado. No se pasan pedidos, clientes ni sesiones de base de datos, así que los plug-ins no pueden depender de las partes internas del núcleo. El contrato se convierte en un paquete pequeño, versionado por separado. Cada plug-in declara la versión que necesita, y el registro lo comprueba al arrancar, así que una discrepancia aparece en el despliegue, no como un impuesto erróneo en un recibo. Si no existe ningún plug-in para un país, el proceso de pago rechaza el pedido en lugar de cobrar un impuesto cero. Por último, un único conjunto compartido de pruebas de contrato se ejecuta contra cada plug-in. Añadir un cuarto país es ahora un paquete nuevo y sus pruebas, no un cambio en el proceso de pago.

Así se ve en tiempo de ejecución. El proceso de pago pide al registro el plug-in registrado para Alemania, y lo recibe. Después llama a ese plug-in a través del contrato. Fíjate en el plug-in de Estados Unidos. Se construyó para una versión principal anterior del contrato, así que el registro lo omitió al arrancar, con un mensaje de log, en lugar de hacer caer el proceso de pago.

Tres cosas mantienen sana una arquitectura de plug-ins. Primero, el aislamiento. Los plug-ins en proceso son rápidos, pero uno con una fuga de memoria afecta a todo. Las normas fiscales de tu propio equipo pueden ejecutarse en proceso; el código arbitrario de un marketplace público no debería. Segundo, el versionado. Prefiere los cambios aditivos, como los campos opcionales nuevos, para que los plug-ins antiguos sigan funcionando. Tercero, la ley de Hyrum: con suficientes usuarios, alguien dependerá de cada comportamiento observable de una interfaz. Así que mantén pequeño el contrato y trata todo lo que pueden ver los plug-ins como parte de él. Y no construyas puntos de extensión para necesidades imaginadas. Espera a haber construido el mismo tipo de variación dos o tres veces y, entonces, extrae el contrato de los casos reales.

La sección completa explica en qué se diferencia de un microkernel de sistema operativo, enumera los antipatrones comunes y muestra el contrato y el registro en código. A continuación, en la Sección cinco punto seis: arquitectura hexagonal, de cebolla y limpia. Nos vemos allí.

Lo que necesitas saber

Definición y origen

Una arquitectura de microkernel, también llamada arquitectura de plug-ins, divide una aplicación en dos tipos de parte: un sistema central (core system) que contiene la funcionalidad mínima y general necesaria para funcionar, y plug-ins que añaden o cambian comportamiento sin que se modifique el núcleo. Los plug-ins se conectan al núcleo solo mediante contratos publicados, y el núcleo los localiza a través de un registro. Mark Richards y Neal Ford la catalogan como uno de los estilos arquitectónicos clásicos en Fundamentals of Software Architecture, y encaja con la descripción de estilo arquitectónico dada en la Sección 5.1: un conjunto con nombre de restricciones sobre los elementos (núcleo, plug-ins) y los conectores (contratos) de un sistema.

La idea nació en el software de producto: herramientas que instalan muchos clientes, cada uno de los cuales quiere algo ligeramente distinto. En lugar de mantener una versión separada para cada cliente, el proveedor distribuye un solo núcleo y deja que los clientes, los socios o los propios equipos del proveedor añadan plug-ins. Los entornos de desarrollo integrados, los navegadores web, los editores gráficos y los paquetes de contabilidad funcionan así. El nombre se toma de los sistemas operativos, donde un microkernel es un núcleo muy pequeño con todo lo demás ejecutándose fuera de él; las dos ideas están relacionadas, pero no deben confundirse, como se explica más adelante.

Una aplicación de microkernel suele ser una única unidad de despliegue, así que pertenece a los estilos monolíticos de este capítulo: una unidad de despliegue, un proceso, un conjunto de inquietudes operativas. Los plug-ins pueden instalarse por separado, pero en tiempo de ejecución viven dentro del núcleo o justo a su lado.

La forma de un sistema de microkernel

Arquitectura de microkernel: un sistema central con un registro de plug-ins, y plug-ins que se conectan al núcleo solo mediante contratos publicadosUna aplicación desplegable (plug-ins cargados al arrancar o en ejecución)Núcleológica mínima y generalReg. de plug-insid, versión, contratocontratocontratoPlug-in AImpuestos RUPlug-in BImpuestos AlemaniaPlug-in CImpuestos EEUUPlug-in DPago c/ tarjetaPlug-in EPago c/ billeteraPlug-in FPago al recibirLos plug-ins nunca se llaman entre sí; el núcleo nunca nombra a unoFlechas: cada plug-in implementa un contrato que publica el núcleo
El sistema central es dueño del flujo de trabajo general y de un registro; cada plug-in implementa un contrato que publica el núcleo, y los plug-ins nunca dependen unos de otros.

Cómo funciona: núcleo, plug-ins, registro y contratos

El sistema central contiene lo que necesita toda instalación: el flujo de trabajo principal, servicios compartidos como el logging o la configuración, y los puntos de extensión donde se acoplan los plug-ins. Richards y Ford lo describen como el camino feliz a través de la aplicación, con poco o ningún procesamiento personalizado; en el proceso de pago de una plataforma de reparto de comida, eso es poner precio a un carrito, cobrar el pago y confirmar el pedido. Un buen núcleo es deliberadamente aburrido. Cada caso especial que varía según el cliente, el país o la línea de producto es candidato a salir de él.

Los plug-ins son módulos independientes y autocontenidos que contienen procesamiento especializado. Deben depender solo del contrato, no unos de otros ni de las clases internas del núcleo. Cuando un plug-in necesita algo que proporciona otro, esa necesidad debe cubrirla el núcleo (como un servicio que expone a través del contrato) o bien hay que fusionar los dos plug-ins; las dependencias de plug-in a plug-in convierten sin hacer ruido una estrella limpia en una maraña.

El registro anota qué plug-ins están disponibles, cómo se llama cada uno, qué versión es, qué contrato implementa y cómo llegar a él. Puede ser tan sencillo como un diccionario rellenado al arrancar, un directorio en el que el núcleo busca manifiestos, entradas en los metadatos de un paquete (como con los puntos de entrada de Python), una tabla de base de datos o un servicio remoto que lista plug-ins desplegados en otro lugar. El registro es lo que permite al núcleo decir "dame las normas fiscales de Alemania" sin saber que existe una clase llamada GermanTax.

Los contratos definen qué debe proporcionar un plug-in y de qué puede depender: una interfaz con firmas de métodos, las estructuras de datos que la cruzan, el comportamiento ante errores y una versión. En Eclipse, un contrato es un punto de extensión declarado en el manifiesto de un plug-in con un esquema; en Visual Studio Code es la API de extensiones más los puntos de contribución enumerados en el package.json de una extensión; en tu propio código suele ser una interfaz (o un Protocol de Python) en un pequeño paquete propio. Cuando el plug-in procede de otra organización y no puede seguir tu contrato, un adaptador traduce entre ambos, exactamente como en la arquitectura hexagonal.

Los plug-ins pueden conectarse de tres maneras. Las llamadas punto a punto, en proceso son lo habitual: el núcleo llama a un método del objeto plug-in. Los plug-ins en un proceso separado se ejecutan en su propio proceso y hablan con el núcleo mediante mensajes entre procesos, lo que protege al núcleo de los bloqueos. Los plug-ins remotos están detrás de una interfaz HTTP o de mensajería; esto permite plug-ins en otros lenguajes o de otros equipos, pero añade latencia de red y modos de fallo, y el sistema empieza a parecerse a los estilos distribuidos del Capítulo 6.

Ejemplos reales

Eclipse. El IDE Eclipse es el caso de manual. Su núcleo es poco más que un banco de trabajo y un entorno de ejecución de plug-ins (construido sobre Equinox, una implementación de la especificación del framework OSGi). Casi todo lo que ve un desarrollador, desde las herramientas de Java hasta la integración con el control de versiones, es un plug-in. Cada plug-in tiene un manifiesto que declara lo que necesita, qué puntos de extensión ofrece a otros y a qué extensiones se conecta. Los plug-ins se activan de forma diferida, solo cuando algo los necesita de verdad, para que cientos de plug-ins instalados no ralenticen el arranque.

Visual Studio Code. Las extensiones declaran eventos de activación (abrir un archivo Markdown, ejecutar un comando) para cargarse solo cuando hace falta, y aportan comandos, lenguajes, temas y vistas a través de su manifiesto. Las extensiones se ejecutan en un proceso anfitrión de extensiones separado, de modo que una extensión que se comporta mal no puede congelar la interfaz de usuario del editor. Es un buen ejemplo de elegir el aislamiento por procesos para un ecosistema de plug-ins que incluye miles de autores externos.

Navegadores web. Las extensiones de navegador usan un conjunto estándar de API de extensiones y deben declarar en un manifiesto los permisos que necesitan (qué sitios pueden leer, si pueden ver las pestañas o las descargas). El navegador hace cumplir esos permisos, porque por defecto no se puede confiar en los autores de extensiones.

Motores de reglas de negocio. El sistema de siniestros de una aseguradora puede mantener el flujo de trabajo de los siniestros en el núcleo y poner las normas de cada jurisdicción (qué está cubierto, qué documentos se necesitan, los plazos legales) en plug-ins. Los motores fiscales, los sistemas de nóminas y las plataformas de comercio electrónico con plug-ins de métodos de pago siguen el mismo patrón: un proceso estable, con normas o proveedores que varían según el país, el producto o el cliente.

Herramientas para desarrolladores. El framework de pruebas pytest está construido en torno a la biblioteca de plug-ins pluggy; muchas de sus propias funcionalidades están implementadas como plug-ins que usan los mismos hooks que usan terceros. Las herramientas de compilación, los linters y los generadores de sitios estáticos suelen hacer lo mismo.

Microkernel de aplicación frente a microkernel de sistema operativo

AspectoMicrokernel de sistema operativoArquitectura de microkernel de aplicación (de plug-ins)
Qué es mínimoEl kernel: espacios de direcciones, hilos, planificación y comunicación entre procesosEl sistema central: el flujo de trabajo general de una aplicación
Qué queda fuera del núcleoControladores de dispositivos, sistemas de archivos y pilas de red, ejecutados como servidores separados en espacio de usuarioComportamiento de negocio o funcionalidades variables, como normas fiscales, métodos de pago o lenguajes de un editor
Objetivos principalesFiabilidad, seguridad y una base de cómputo de confianza pequeñaExtensibilidad, personalización y un ecosistema de terceros
Cómo se comunican las partesPaso de mensajes entre procesos aisladosNormalmente, llamadas en proceso a través de una interfaz; a veces, un proceso separado o una llamada remota
EjemplosMach, la familia L4 (incluido seL4, verificado formalmente), QNX, MINIX 3Eclipse, Visual Studio Code, navegadores web, pytest, motores de reglas de seguros y fiscales

Aislamiento, versionado y compatibilidad de los plug-ins

Tres preguntas deciden si una arquitectura de plug-ins se mantiene sana con los años: cuán aislados están los plug-ins, cómo evoluciona el contrato y cómo se detecta la incompatibilidad.

Aislamiento. Los plug-ins en proceso comparten la memoria, los hilos y los permisos del núcleo. Eso es rápido y sencillo, pero un plug-in con un bucle infinito, una fuga de memoria o un agujero de seguridad afecta a todo. Las opciones, de menor a mayor aislamiento y costo, son: en proceso con llamadas defensivas (timeouts, captura de excepciones por plug-in, límites de recursos); un proceso o worker separado por plug-in o por grupo de plug-ins; un sandbox, como un entorno de ejecución de WebAssembly o un intérprete restringido, que limita lo que puede tocar el código del plug-in; y un servicio remoto. Elige preguntándote quién escribe los plug-ins. Las normas fiscales de tu propio equipo pueden ejecutarse en proceso; el código arbitrario de un marketplace público no debería.

Versionado. El contrato es una API, y se aplican las reglas de la Sección 0.6 sobre los cambios incompatibles. Versiona el contrato por separado del núcleo, normalmente con versionado semántico: una nueva versión principal para los cambios incompatibles y una versión menor para las incorporaciones compatibles hacia atrás. Deja que cada plug-in declare para qué versión del contrato se construyó, y que el núcleo declare qué versiones admite. El cambio aditivo (campos opcionales nuevos, hooks opcionales nuevos) mantiene funcionando los plug-ins antiguos; cuando un cambio incompatible es inevitable, admite la versión principal antigua y la nueva en paralelo durante un periodo de obsolescencia anunciado.

Comprobaciones de compatibilidad. Comprueba la compatibilidad cuando se cargan los plug-ins, no cuando un cliente los activa por primera vez. Un registro que rechaza un plug-in incompatible al arrancar, con un mensaje de log claro, convierte un error en tiempo de ejecución en un fallo de despliegue. Merece la pena recordar aquí la observación de Hyrum Wright, conocida como ley de Hyrum: con suficientes usuarios, alguien dependerá de cada comportamiento observable de una interfaz, diga lo que diga la documentación. Mantén el contrato pequeño, deja las partes internas del núcleo fuera de alcance (Eclipse marca como internal los paquetes que no son API por esta razón) y trata todo lo que ven los plug-ins como parte del contrato.

Características del estilo microkernel

CaracterísticaValoraciónMotivo
Extensibilidad y modificabilidad (en las direcciones previstas)AltoEl comportamiento nuevo llega como un plug-in nuevo; ni el núcleo ni los demás plug-ins cambian.
SencillezMediaLa idea es sencilla, pero los contratos, un registro, la carga y las comprobaciones de versión son maquinaria real.
Capacidad de prueba (testabilidad)De media a altaCada plug-in puede probarse por separado contra el contrato; el núcleo puede probarse con plug-ins falsos. Las combinaciones siguen necesitando pruebas.
DesplegabilidadMediaA menudo se pueden añadir plug-ins sin volver a desplegar el núcleo; un cambio de contrato obliga a lanzamientos coordinados.
RendimientoDe media a altaLos plug-ins en proceso son llamadas a funciones normales; el aislamiento por proceso o sandbox añade sobrecarga.
Escalabilidad y elasticidadBajoSuele ser una sola unidad de despliegue, así que escala en bloque, como cualquier monolito.
Tolerancia a fallosDe baja a mediaUn plug-in en proceso puede hacer caer o ralentizar al anfitrión; el aislamiento por procesos lo mejora a cambio de un costo.
Costo totalAlta (barato)Una sola base de código y un solo entorno de ejecución que operar; el costo principal es diseñar y hacer evolucionar los contratos.

Cuándo usarlo y cuándo no

Buenos casos. Software de producto que muchos clientes instalan y personalizan. Sistemas con un flujo de trabajo estable pero muchas variantes de un paso: impuestos y nóminas por país, normas de seguros por jurisdicción, métodos de pago, importadores de formatos de archivo, empresas de transporte. Plataformas que quieren un ecosistema de terceros. Sistemas en los que un regulador o un cliente cambia las normas a menudo y quieres que esos cambios sean lanzamientos pequeños y probados por separado. El estilo también funciona dentro de un sistema mayor: un servicio de una arquitectura distribuida puede ser en sí mismo un microkernel.

Malos casos. Sistemas en los que aún no sabes qué varía. Los puntos de extensión diseñados de antemano para necesidades imaginadas son generalidad especulativa: añaden maquinaria, frenan al equipo y suelen estar en el lugar equivocado cuando llega la necesidad real (consulta el análisis de la reversibilidad en la Sección 1.6). Sistemas que necesitan una escalabilidad o un aislamiento de fallos muy altos, ya que todo escala y falla como una sola unidad. Sistemas en los que los plug-ins tendrían que coordinarse estrechamente entre sí, lo que anula el principal beneficio del estilo.

Una regla práctica: espera a haber construido el mismo tipo de variación dos o tres veces y, entonces, extrae el contrato de los casos reales. Un contrato derivado de tres implementaciones fiscales que funcionan será mucho mejor que uno adivinado antes de la primera.

Ejemplo práctico

Ejemplo práctico: diseñar un contrato de plug-in para normas fiscales de cada país

La plataforma de reparto de comida que se usa a lo largo de este curso se lanza en Alemania y Estados Unidos después del Reino Unido. Cada país grava de forma distinta la comida, el alcohol y las tarifas de envío, y el impuesto sobre las ventas de EE. UU. además varía según el estado y la ciudad. El código del proceso de pago ya contiene ramas if country == "GB". El arquitecto propone un microkernel: el proceso de pago es el núcleo, y las normas de cada país son un plug-in. Estas son las decisiones, en el orden en que se tomaron.

1. Nombrar el punto de variaciónSolo el cálculo del impuesto sobre un carrito con precio varía según el país. Los precios, los descuentos, el pago y los recibos se quedan en el núcleo. Un punto de variación estrecho mantiene pequeño el contrato.
2. Decidir qué cruza el contratoEntradas: líneas del carrito con una categoría neutral (hot-food, alcohol, delivery-fee), importes netos como Decimal y una región opcional. Salida: el impuesto total y un desglose etiquetado para el recibo. No se pasan objetos del núcleo (pedidos, clientes, sesiones de base de datos), así que los plug-ins no pueden depender de las partes internas del núcleo.
3. Hacer del contrato un paquete separadotax_contract contiene solo clases de datos y el protocolo TaxRules, versionado con versionado semántico. Tanto el núcleo como los plug-ins dependen de él; ninguno depende del otro.
4. Definir la compatibilidadUn plug-in declara la versión del contrato que necesita, por ejemplo (2, 1). El núcleo lo acepta si la versión principal coincide y la versión menor del núcleo es al menos igual de alta. Añadir un campo opcional es un cambio menor; renombrar o eliminar uno es un cambio principal.
5. Elegir el descubrimientoLos plug-ins son paquetes normales de Python que se registran en el grupo de puntos de entrada fooddelivery.tax_rules. Basta con instalar un paquete; ningún código del núcleo enumera los países.
6. Decidir el comportamiento ante fallosSi no hay ningún plug-in registrado para un país, el proceso de pago rechaza el pedido con un error claro en lugar de cobrar un impuesto cero. Un plug-in que lanza una excepción hace fallar solo ese proceso de pago, y el error se notifica con el nombre y la versión del plug-in.
7. Decidir el aislamientoLos plug-ins los escriben los propios equipos de país de la empresa y son de confianza, así que se ejecutan en proceso por velocidad. Si se permitiera que proveedores fiscales externos suministraran plug-ins, el diseño los pasaría detrás de una llamada remota con un timeout.
8. Especificar las pruebasUn conjunto compartido de pruebas de contrato (redondeo, carritos vacíos, líneas de reembolso negativas, categorías desconocidas) se ejecuta contra cada plug-in en su propio pipeline. Cada equipo de país añade sus propios casos a partir de las orientaciones fiscales publicadas.

El núcleo ya no cambia cuando se añade un país, así que el lanzamiento de un cuarto país es un paquete nuevo más sus pruebas, no un cambio en el proceso de pago.

La decisión más difícil es la del paso 2. Una lista de categorías demasiado gruesa obliga a los plug-ins a adivinar; una demasiado detallada acopla el contrato a la ley de un solo país. Cuenta con revisarla, y planifica la migración de versión principal cuando lo hagas.

El registro rechaza los plug-ins incompatibles al arrancar, así que una discrepancia de versión aparece en una comprobación del despliegue, no como un impuesto erróneo en el recibo de un cliente.

# package: tax_contract  (version 2.1.0)
from dataclasses import dataclass
from decimal import Decimal
from typing import Protocol

CONTRACT_VERSION = (2, 1)

@dataclass(frozen=True)
class TaxLine:
    sku: str
    category: str      # "hot-food", "cold-food", "alcohol", "delivery-fee"
    net: Decimal       # never float for money

@dataclass(frozen=True)
class TaxResult:
    total_tax: Decimal
    breakdown: dict[str, Decimal]   # label shown on the receipt

class TaxRules(Protocol):
    country: str                    # ISO 3166-1 alpha-2, e.g. "DE"
    requires_contract: tuple[int, int]
    def calculate(self, lines: list[TaxLine], region: str | None) -> TaxResult: ...
El paquete del contrato, versionado y publicado por separado tanto del núcleo como de los plug-ins.
from importlib.metadata import entry_points
from tax_contract import CONTRACT_VERSION

class UnsupportedCountry(Exception): pass

class TaxRegistry:
    def __init__(self, log):
        self._by_country = {}
        for ep in entry_points(group="fooddelivery.tax_rules"):
            plugin = ep.load()()
            major, minor = plugin.requires_contract
            if major != CONTRACT_VERSION[0] or minor > CONTRACT_VERSION[1]:
                log.warning("skipping %s: needs contract %s", ep.name, plugin.requires_contract)
                continue
            self._by_country[plugin.country] = plugin

    def for_country(self, country):
        if country not in self._by_country:
            raise UnsupportedCountry(country)   # fail loudly, never guess a rate
        return self._by_country[country]
El registro en el núcleo: descubre los plug-ins instalados, comprueba la compatibilidad del contrato y los busca por país.

Los plug-ins fiscales en tiempo de ejecución

Ejemplo práctico: el proceso de pago pide al registro fiscal el plug-in registrado para el país del cliente y lo llama a través del contrato TaxRulesPagocaso de usoTaxRegistrypaís a plug-in1 for("DE")2 plug-inTaxRulescontrato v2.13 calculate(lines)gb_tax 2.0.4pide v2.0de_tax 1.3.0pide v2.1us_tax 3.1.0pide v1.4omitido: versiónprinc. erróneaCarga: al arrancar, el registro lee los paquetes instalados (puntos de entrada), compruebala versión de contrato que requiere cada plug-in y lo registra por país; los incompatiblesse anotan en el log y se omiten sin hacer caer el pago.registrados porcódigo de país
El proceso de pago pide al registro el plug-in registrado para un país y lo llama a través del contrato; un plug-in construido para una versión de contrato incompatible se omite al arrancar.

Hacer cumplir el estilo en el código

Una arquitectura de plug-ins solo sigue siéndolo si sus reglas se comprueban automáticamente. La estructura que funciona en la mayoría de los lenguajes tiene tres tipos de paquete: un pequeño paquete de contrato, solo con interfaces y tipos de datos; el núcleo, que depende del contrato; y un paquete por cada plug-in, que depende del contrato y de nada más de lo tuyo.

Después, haz cumplir las reglas de dependencia con herramientas como las que se tratan en la Sección 3.4: en Python, import-linter puede prohibir que el núcleo importe cualquier paquete de plug-in y que los plug-ins se importen entre sí o importen el núcleo; en Java, las pruebas de ArchUnit o los sistemas de módulos (JPMS, OSGi) pueden hacer lo mismo. Haz fallar la compilación cuando se incumpla una regla.

Añade un conjunto de pruebas de contrato: un conjunto de pruebas escritas una sola vez contra el contrato y ejecutadas contra cada plug-in en el pipeline propio de ese plug-in. Comprueba el comportamiento que promete el contrato (redondeo, entrada vacía, tipos de error), no la ley fiscal de cada país, que cubren las pruebas propias del plug-in. Por último, añade una comprobación al arrancar o una prueba de humo del despliegue que cargue cada plug-in instalado y verifique su versión de contrato, para detectar las incompatibilidades antes de que las encuentren los usuarios.

Preguntas frecuentes

¿Un microkernel no es más que el patrón estrategia?

A nivel de código, el patrón estrategia es el mismo mecanismo: elegir en tiempo de ejecución una implementación de una interfaz. El microkernel es la versión arquitectónica: los plug-ins se construyen y versionan por separado, y a menudo se instalan por separado, un registro los descubre, y el contrato es una API publicada con reglas de compatibilidad.

¿Pueden los plug-ins tener sus propios datos?

Sí, y a menudo deberían, pero trátalos como los datos propios de cada módulo en un monolito modular: las tablas o los archivos de un plug-in le pertenecen, y el núcleo llega a ellos solo a través del contrato.

¿Debería la configuración sustituir a los plug-ins?

Para diferencias pequeñas y de tipo dato (una tasa, un umbral, una etiqueta), sí: la configuración es más barata y más segura. Los plug-ins son para diferencias que necesitan lógica. Muchos sistemas usan ambos: un plug-in por país más configuración para las tasas vigentes de cada país.

Comprueba lo que has entendido

¿Qué tres cosas debe definir una arquitectura de plug-ins además del propio núcleo?

Los plug-ins, los contratos que implementan y un registro que permite al núcleo descubrir y buscar plug-ins sin nombrarlos en el código.

¿Por qué los plug-ins no deben depender unos de otros?

Porque entonces ya no pueden instalarse, eliminarse, probarse ni versionarse de forma independiente, que es el principal beneficio del estilo; las necesidades compartidas van en el núcleo o en el contrato.

¿Por qué Visual Studio Code ejecuta las extensiones en un proceso separado?

Para que una extensión lenta o que se bloquea no pueda congelar ni hacer caer la interfaz de usuario del editor: cambia algo de rendimiento y de complejidad por aislamiento frente a miles de autores externos.

¿Cuándo es mejor la configuración que un plug-in?

Cuando la diferencia entre variantes son datos, como una tasa, un límite o una etiqueta, y no lógica. La configuración es más sencilla y más segura de cambiar.

Siguiente · 5.6Hexagonal y limpiaPuertos y adaptadores, arquitectura de cebolla y limpia: mantener la lógica de negocio en el centro con las dependencias apuntando hacia dentro, puertos conductores y conducidos, un ejemplo en Python con un adaptador real y un doble de prueba, y los costos.Continuar →

Fuentes y lecturas recomendadas

¿Tienes una pregunta sobre esta sección? ArchitectBot, el asistente de estudio con IA del curso, responde primero a partir del curso.

Pregunta: “Vendemos una app de servicio técnico de campo a 40 clientes y cada uno quiere reglas personalizadas para planificar los trabajos. ¿Deberíamos construir una arquitectura de plug-ins, o basta con la configuración?”