Skip to main content
El Fetcher Engine es el núcleo de extracción de Fetcher, empaquetado como un módulo Go que puedes importar. Aplicaciones anfitrionas como Matcher y Reporter ejecutan el Engine en su propio proceso, en lugar de operar un despliegue separado de Fetcher. El Manager y el Worker standalone son ellos mismos hosts sobre el mismo Engine. El Engine es dueño de las reglas de la extracción: ciclo de vida de las conexiones, descubrimiento y validación de esquemas, planificación de consultas, ejecución de la extracción, contratos de resultado y de error, límites y seguridad por tenant. No es dueño de ninguna infraestructura.
El Engine es un módulo Go distinto de los servicios de Fetcher. Su ruta de módulo es github.com/LerianStudio/fetcher/pkg/engine, y la de los servicios es github.com/LerianStudio/fetcher/v2. El Engine tiene su propia línea de versiones, con tags prefijados por ruta (pkg/engine/vX.Y.Z). Importar el Engine no arrastra ninguna dependencia de los servicios.Fetcher es source-available bajo la Elastic License 2.0, y el Engine lleva la misma licencia. Puedes leer las reglas de extracción que embebes.

El modelo de tres capas


El principio rector: el Engine es dueño de lo que hace que Fetcher sea Fetcher, y las aplicaciones anfitrionas son dueñas de cómo corre Fetcher. Por eso, cada producto que embebe el Engine comparte un único dueño canónico del comportamiento de datasources y de extracción. Un cambio de regla en el núcleo llega a todos los hosts en el siguiente salto de módulo, y ningún host lo reimplementa.

La frontera de importación


El módulo del Engine declara cero dependencias de terceros. Su go.mod no tiene bloque require, y un test lo mantiene así en cada compilación. Dos guardas corren dentro del módulo en cada go test ./..., y el workflow de CI del módulo las ejecuta con el workspace de Go desactivado:
  • La lista de permitidos. Cada dependencia transitiva de pkg/engine debe ser un paquete de la biblioteca estándar de Go o un paquete local del módulo del Engine. Cualquier otra familia de imports falla la compilación, incluso una familia que ninguna lista de denegados nombre.
  • Una lista de denegados explícita. Clases enumeradas fallan además de la lista de permitidos. Cubren frameworks HTTP, brokers de mensajes, drivers de bases de datos, SDKs de object storage, middleware de runtime multi-tenant, las bibliotecas de autenticación y de licencia, y envolturas de la biblioteca estándar como database/sql, net/http y os/exec.
Un paso de CI aparte busca una línea require en el go.mod del Engine y falla el job cuando la encuentra.

Por qué esto importa cuando embebes

  • Sin conflictos de dependencias. El Engine no puede meter un driver, un cliente de broker o un framework HTTP en el grafo de tu módulo. Tu host mantiene el control total de sus propias versiones.
  • Sin E/S oculta. El núcleo no puede abrir un socket, un archivo ni una base de datos por su cuenta. Cada byte que entra y sale cruza un puerto que tú proveíste.
  • Un punto de sustitución estable. Ejecuta todo el Engine contra la implementación en memoria en tus tests. Cambia a los adaptadores reales en producción, sin tocar el código que lo llama.

Modo Direct y modo Store


Un plan de extracción lleva un modo con tres valores: direct, store y el valor cero auto. El Engine resuelve auto a partir de los puertos que conectaste. Con un ResultSink configurado elige el modo Store, y sin él elige el modo Direct. Así, el modo es una consecuencia de tu composición, no un interruptor aparte. Una petición explícita de store sin sink falla de entrada con un error de validación, antes de que el Engine toque ningún datasource. Los dos modos devuelven formas distintas. Exactamente una rama del resultado es no nula, y el JSON omite por completo la rama sin usar.

Modo Direct

El Engine ejecuta los pasos del plan y fusiona las filas en un solo mapa. Las claves del mapa son el nombre de la configuración del datasource y luego la tabla calificada. El Engine serializa ese mapa una vez como JSON indentado y devuelve los bytes inline.
El resultado inline lleva estos metadatos: La salida del modo Direct es determinista. El serializador ordena las claves del mapa. Por eso, la misma entrada te da un JSON idéntico byte a byte y el mismo digest, sin importar en qué orden terminaron los pasos paralelos. Tu host es dueño de todo lo que viene después. El Worker de Fetcher, por ejemplo, firma el texto plano con HMAC-SHA256 y lo cifra antes de guardar los bytes.

Modo Store

El Engine abre un stream en tu ResultSink y escribe el resultado de forma incremental, en memoria constante. Nunca retiene el resultado completo. Un único escritor drena las goroutines de extracción estrictamente en orden ascendente de paso del plan, y el digest de integridad cubre exactamente los bytes escritos. La forma en el cable es NDJSON — un objeto JSON por línea, terminada en salto de línea, sin array que lo envuelva:
La llamada devuelve una referencia en lugar de bytes: Ante un aborto — un error de escritura, un límite de tamaño superado o un contexto cancelado — el Engine abandona el escritor y nunca llama a Close. Por eso un resultado parcial nunca se convierte en una referencia devuelta. Trata un escritor sin cerrar como una escritura descartada.

Contratos de fallo y de resultado


  • Fallo inmediato entre datasources. El primer paso que falla detiene la corrida. El Engine nunca devuelve un resultado parcial.
  • Errores redactados. Un error de driver puede llevar dentro un DSN, una credencial o detalles internos del driver, así que el Engine lo descarta y devuelve un mensaje fijo en su lugar.
  • Once categorías de error. validation, not_found, unauthorized, forbidden, limit_exceeded, conflict, unavailable, connect, timeout, canceled e internal. Tu host las mapea a sus propios códigos de transporte. connect se mantiene distinto de unavailable, y timeout se mantiene distinto de canceled.
  • Cinco estados de ejecución. pending, running, completed, failed y canceled. Los tres últimos son terminales. Una cancelación del host registra canceled, y un plazo excedido registra failed.
  • Conectores cerrados. Cada conector que abre el Engine se vuelve a cerrar, en el camino de éxito y en cada camino de fallo.

Límites y alcance por tenant


El Engine nunca corre sin límites. Cuando no aportas ninguno, aplica estos valores por defecto: Una petición puede bajar cualquier límite, y nunca subirlo. Un override por encima del valor por defecto falla con un error de validación que nombra el campo infringido. Un override cero o negativo mantiene el valor por defecto. El Engine aplica el techo de tamaño del resultado dos veces: una cota inferior barata por paso, que aborta temprano, y una comprobación autoritativa sobre el payload indentado final. Un resultado por encima del límite nunca te llega inline ni llega a tu sink. El alcance por tenant es igual de estrecho. Cada operación lleva un tenant ID y nada más — el Engine no tiene concepto de organización ni de producto. Valida el tenant ID antes de cualquier acceso a recursos, y ahí rechaza un valor vacío o mal formado.

Próximos pasos


Embeber el Engine

Impórtalo, provee los puertos y constrúyelo con un ejemplo ejecutable.

Referencia de puertos

Cada puerto, si es obligatorio y qué pasa sin él.