Planteamiento y contexto
Una plataforma debe cargar plugins compilados a partir de diferentes lenguajes y componerlos en múltiples runtimes. Explica cómo definir interfaces en WIT, componer componentes, manejar cadenas y recursos, y probar la Canonical ABI, los errores y la evolución de versiones.
Qué está evaluando el entrevistador
- Distinguir la capa del módulo base (core module) de WebAssembly de la capa de interfaz del Component Model.
- Comprender WIT como una descripción de interfaces neutral respecto al lenguaje y la Canonical ABI como la representación entre componentes de tipos de alto nivel.
- Explicar la propiedad de recursos (resource ownership), el préstamo (borrowing), los errores de tipo result y los ciclos de vida asíncronos.
- Considerar las versiones de los componentes, el aislamiento de capacidades (capabilities), los adaptadores, las diferencias de runtime y las pruebas de compatibilidad.
Preguntas de clarificación que debes hacer
- ¿Qué tipos cruzan el límite del plugin: archivos grandes, streams, handles o recursos de larga duración?
- ¿Qué capacidades de WASI otorga el runtime y deben los plugins estar aislados de archivos, redes o claves?
- ¿Las llamadas son síncronas o asíncronas, y los errores deben recuperarse, reintentarse o terminar el componente?
- ¿Cómo se publican las versiones de WIT, puede un componente antiguo componerse con un host nuevo y quién es el dueño de la matriz de compatibilidad?
Una respuesta de 30 segundos
Escribiría el contrato entre lenguajes más pequeño en WIT y generaría bindings para cada lenguaje. Los componentes intercambian cadenas, listas, results y recursos a través de interfaces del Component Model y la Canonical ABI, en lugar de exponer la disposición de memoria de un lenguaje. El runtime otorga solo las capacidades requeridas de archivos o red; el host es responsable de la cancelación, los límites de tiempo (deadlines), el mapeo de errores y la limpieza. La evolución de versiones utiliza campos aditivos y pruebas de compatibilidad, mientras que los detalles de la ABI del módulo base siguen siendo específicos de la implementación.
Análisis detallado paso a paso
1. Separar los módulos base de la interfaz del componente
Los módulos base exponen memoria lineal, funciones y tipos numéricos. El Component Model construye encima interfaces de mayor nivel, dependencias y composición. Una API entre lenguajes debe detenerse en la interfaz del componente, sin exponer la dirección de memoria de un compilador, la disposición de una estructura o el nombre de una función exportada como un contrato público.
2. Expresar tipos neutrales respecto al lenguaje en WIT
WIT describe interfaces, worlds, recursos y errores de tipo result; las herramientas generan bindings para Rust, Go y otros lenguajes. Este ejemplo muestra un handle de recurso; valida la sintaxis exacta con la cadena de herramientas (toolchain) de destino:
package example:plugin;
interface store {
resource session;
open: func(name: string) -> result<session, string>;
read: func(s: borrow<session>) -> result<list<u8>, string>;
}La interfaz expone la semántica de negocio mientras que el almacenamiento, el manejo de hilos (threading) y la asignación de memoria siguen siendo detalles de implementación.
3. Comprender la Canonical ABI y los ciclos de vida de los recursos
La Canonical ABI especifica cómo cadenas, listas, results y otros valores de alto nivel cruzan el límite de un componente utilizando representaciones base de WebAssembly. El modelo de componentes gestiona la propiedad y el préstamo de recursos; el host debe definir el cierre, la cancelación y la limpieza. Un handle numérico no es un puntero inmortal. Para datos grandes, evalúa los costos de copiado, streaming y contrapresión (backpressure).
4. Diseñar errores, capacidades y versiones
Modela los fallos de negocio esperados como valores result o enums de error explícitos, distinguiendo los errores reintentables de los fallos de autorización. Otorga solo las capacidades de WASI necesarias y limita el tiempo de ejecución, la memoria, la concurrencia y el tamaño de salida. Haz evolucionar WIT de manera aditiva siempre que sea posible, preserva las pruebas de compatibilidad para worlds antiguos y usa adaptadores para la conversión de versiones sin ocultar cambios semánticos.
Respuesta modelo
Definiría la interfaz de world neutral respecto al lenguaje más pequeña en WIT y generaría bindings. Los componentes se componen a través del Component Model, mientras que la Canonical ABI representa cadenas, listas, results y recursos en el límite; la memoria del módulo base y las exportaciones siguen siendo detalles de implementación. Los recursos utilizan préstamo y semántica de cierre explícito, con el host controlando la cancelación, los plazos, las capacidades y la limpieza. Los errores utilizan tipos result distinguibles y las capacidades siguen el principio de menor privilegio. Cada cambio en WIT ejecuta una matriz de compatibilidad entre lenguajes, con adaptadores que manejan la conversión de versiones para que los componentes antiguos no se vinculen silenciosamente a nuevas semánticas.
Errores comunes
- Compartir la memoria lineal del módulo base o estructuras del lenguaje directamente, eludiendo la interfaz del componente.
- Tratar a WIT como el archivo de cabecera de un solo lenguaje e ignorar los bindings generados y la semántica de tipos.
- Tratar un handle de recurso como un puntero sin procesar (raw pointer) sin reglas de préstamo, cierre, cancelación y limpieza.
- Probar solo el camino feliz (happy path) y omitir errores de tipo result, contrapresión, plazos y capacidades denegadas.
- Otorgar a un plugin acceso a todo el sistema de archivos o la red en lugar de un límite de menor privilegio.
- Compilar solo el host después de un cambio en WIT y omitir las pruebas de compatibilidad de componente antiguo con host nuevo.
Preguntas de seguimiento y respuestas
¿Cuándo usarías un stream en lugar de una lista de bytes?
Usa un stream cuando los datos puedan ser grandes, deban procesarse de forma incremental o necesiten un pico de memoria acotado. Define la contrapresión, la finalización y la cancelación. Una configuración o resultado pequeño y acotado puede usar una lista tras validar su tamaño y ciclo de vida.
¿Cómo detendrías a un plugin malicioso que consume recursos?
Limita las capacidades, la memoria, el tiempo de ejecución, la concurrencia y el tamaño de salida en el runtime, y proporciona mecanismos de cancelación y aislamiento en el host. Monitorea la duración de las llamadas, la tasa de errores y las cuotas; termina el plugin y realiza la limpieza si ocurre una infracción.
¿Cómo se mantienen compatibles los cambios en WIT?
Prioriza interfaces o campos opcionales y preserva el significado de los tipos existentes. Mantén versiones antiguas de worlds y pruebas entre lenguajes, utilizando adaptadores cuando sea necesario. Eliminar o modificar la semántica requiere una nueva versión y un plazo límite de migración.