Published

Fase 2 — Data plane sobre R2 (el conector toca nuestra capa de datos)

Connect any source, model it as an ontology, transform it, and operationalize it, analytics, automation and machine learning, under one governed, self-hostable roof. --- Most teams stitch the...

Fase 2 — Data plane sobre R2 (el conector toca nuestra capa de datos)

Documento padre: README.md (charter/roadmap) · Base: fase-1-findings.md
Estado: approach / plan de ejecución.
Naturaleza: en Fase 1 dos conectores movieron un dato ajeno (HttpData de jsonplaceholder). En Fase 2 el conector empieza a mover datos sobre nuestra propia capa de almacenamiento: S3 → R2, el mismo bucket donde vive el lakehouse Iceberg. Aquí "soberanía sobre el dato" deja de ser diapositiva y se vuelve un objeto que aterriza en R2 bajo contrato.


1. Filosofía — qué es y qué no

Qué es: graduar el PoC de conectores-de-sample a nuestro propio conector (services/edc-service/), añadirle el data plane S3 de EDC, y demostrar una transferencia de un objeto (un Parquet, para prefigurar el lakehouse) de S3 a R2 entre dos conectores — primero contra MinIO local (stand-in de R2), y luego contra R2 de verdad (un prefijo de scratch en el bucket del lakehouse). Ese último paso es el money shot: probar que el data plane de EDC escribe en el mismo R2 que ya usa Carbon.

Qué NO es (fuera de alcance, a propósito):

  • ❌ Integración con Carbon/Node (executor, edc-client.ts, UI) → Fase 3.
  • ❌ Semántica de tabla Iceberg / zero-copy vía Lakekeeper → Fase 5. Aquí movemos el Parquet como objeto (Estrategia A, §3.1 del research), no como tabla.
  • ❌ IdentityHub / DID / VC → Fase 6 (seguimos con iam.mock).
  • ❌ Persistencia Postgres + Vault dedicados → Fase 8 (seguimos in-memory).

Por qué este recorte: el objetivo es despejar el riesgo técnico #1 del proyecto — "¿el data plane de EDC habla con R2?" — con el mínimo de piezas alrededor. Todo lo demás se apila después.


2. Objetivos medibles (lo que Fase 2 debe dejar)

  1. Nuestro launcher en services/edc-service/ compila y arranca (build propio, no el del sample).
  2. Transfer S3→S3 verificado entre dos conectores contra MinIO: un objeto aterriza en el bucket destino (comprobado con mc ls/listado).
  3. Transfer contra R2 real verificado: el mismo flujo escribe un objeto en un prefijo del bucket del lakehouse en R2 (dataspace-inbox/), con region=auto + path-style.
  4. Superficie de API recapturada en la versión vigente (revisar v4alpha; documentar deltas vs v3, sobre todo en transfer).
  5. Config de R2 probada y documentada (endpoint, jurisdicción EU, credenciales vía vault/env) — semilla para el despliegue.
  6. fase-2-findings.md con resultados y sondas respondidas.

3. Decisiones de arranque

DecisiónElecciónPor qué
De dónde parte el conectorLauncher propio en services/edc-service/ (nuestro build.gradle.kts), tomando poc/ como referenciaFase 2 es donde "poseemos" el conector. Se acabó depender del árbol de samples.
Módulos del launchercontrol-plane base BOM + data-plane-aws-s3 (Technology-Aws) + iam.mock + management-apiAñadir S3 al set que ya validamos en Fase 1.
Alineación de versionesdata-plane-aws-s3 pineado a la misma release de EDC que el core⚠️ Technology-Aws y el core deben coincidir en versión o el build/runtime rompe.
Stand-in de R2MinIO (S3-compatible, OSS, self-hostable)Iterar sin gastar/ensuciar R2; además on-brand (soberano, self-hostable).
Destino realUn prefijo scratch en el bucket del lakehouse en R2 (dataspace-inbox/…)Prueba el escenario de verdad: EDC escribe donde vive Iceberg.
PayloadUn Parquet pequeñoPrefigura el lakehouse aunque aún lo tratemos como objeto.
Tipo de transferenciaAmazonS3-PUSH (provider empuja al S3 destino)El patrón S3→S3 nativo del data plane.
endpointOverrideEn el DataAddress (source y destination)Es donde EDC lo lee para apuntar a MinIO/R2 (no solo config global).
CredencialesVía vault seeding / .env (gitignored), nunca en el repoR2 keys son secretos; .env fuera de git + inyección al contenedor.
Management APIRevisar v4alpha; mantener v3 donde v4 no esté establev3 deprecada pero v4 aún alpha y con entidades que colisionan. Pin + documentar.

4. Prerrequisitos

  • Lo de Fase 1 (Docker, JDK vía imagen).
  • Credenciales R2: access key / secret + account id + nombre del bucket del lakehouse (las mismas que usa ml-runner, ver S3_ENDPOINT/S3_ACCESS_KEY_ID del lakehouse). Se cargan por .env, no se commitean.
  • mc (MinIO client) o aws CLI dentro de un contenedor auxiliar para verificar buckets (no dependemos del host).

5. Runbook paso a paso

Paso 1 — Launcher propio (services/edc-service/)

Crear nuestro build.gradle.kts a partir de los módulos del poc/ + data-plane-aws-s3. Dockerfile análogo (multi-stage). Arranca provider + consumer igual que en Fase 1, pero ahora con el módulo S3 dentro. Hito: docker compose up con nuestro conector.

Paso 2 — MinIO como R2 local

Añadir MinIO a docker-compose.yml (http://minio:9000, consola :9001). Crear buckets provider-src y consumer-inbox. Subir un sample.parquet a provider-src. Hito: MinIO arriba con el objeto fuente.

Paso 3 — Seed de credenciales S3 en el vault

Sembrar en el vault del conector las claves de acceso a MinIO (patrón SeedVaultExtension de Fase 1), o pasarlas por el DataAddress para el PoC. Hito: el conector puede autenticar contra MinIO.

Paso 4 — Recaptura de API en v4alpha

Antes de crear el asset S3, consultar al conector las versiones de API que expone y revisar los paths v4alpha. Documentar deltas vs v3 (ojo: en transfer v4 se eliminó dataDestination del cuerpo — el destino se maneja distinto). Ajustar payloads. Hito: superficie de API v4 anotada.

Paso 5 — Publicar un Asset S3 (source en MinIO)

Crear el asset con dataAddress tipo AmazonS3 apuntando a provider-src con endpointOverride:

// create-asset (S3) — ilustrativo, verificar contra la versión de API vigente
{
  "@context": { "@vocab": "https://w3id.org/edc/v0.0.1/ns/" },
  "@id": "parquet-asset",
  "properties": { "name": "sample parquet", "contenttype": "application/octet-stream" },
  "dataAddress": {
    "type": "AmazonS3",
    "region": "us-east-1",
    "bucketName": "provider-src",
    "keyName": "sample.parquet",
    "endpointOverride": "http://minio:9000"
  }
}

+ policy + contract definition (como en Fase 1). Hito: el asset S3 aparece en el catálogo.

Paso 6 — Ciclo + transferencia S3→S3 (destino MinIO)

Catálogo → negociación → acuerdo (reutilizamos el flujo de Fase 1). Iniciar transfer AmazonS3-PUSH con destino consumer-inbox:

// destino S3 (según la versión de API; en v4 el destino puede ir aparte)
{
  "type": "AmazonS3",
  "region": "us-east-1",
  "bucketName": "consumer-inbox",
  "keyName": "sample.parquet",
  "endpointOverride": "http://minio:9000"
}

Hito: transfer COMPLETED.

Paso 7 — Verificar el objeto en destino

mc ls consumer-inbox/ → el sample.parquet está ahí, con el mismo tamaño/checksum. Hito: copia S3→S3 demostrada.

Paso 8 — El money shot: contra R2 real

Repetir el paso 6 con el destino apuntando a R2 (prefijo dataspace-inbox/ del bucket del lakehouse):

region=auto
endpointOverride=https://<ACCOUNT_ID>.eu.r2.cloudflarestorage.com   # jurisdicción EU = residencia europea
+ path-style addressing (forcePathStyle)

Verificar el objeto en R2 (mismo bucket que Iceberg). Hito: EDC escribe en el R2 del lakehouse bajo contrato. Riesgo #1 despejado.

Paso 9 — Findings

fase-2-findings.md: versiones, config R2 que funcionó, deltas v3→v4, y las sondas respondidas.


6. R2: gotchas y el ángulo soberanía

GotchaQué hacer
RegiónR2 sólo acepta region=auto (el SDK la exige pero R2 la ignora).
AddressingForzar path-style (forcePathStyle) o saltan 403/HeadBucket.
Endpointhttps://<ACCOUNT_ID>.r2.cloudflarestorage.com.
Residencia EUUsar el endpoint de jurisdicción: https://<ACCOUNT_ID>.eu.r2.cloudflarestorage.com → el dato se queda en la UE. Esto es exactamente el foso: soberanía verificable a nivel de infraestructura. Documentarlo como feature vendible.
Multipart/checksumsR2 soporta S3 pero con matices; validar objetos grandes en Fase 4/8.

7. Entregables (el cimiento que suma)

  1. services/edc-service/ — nuestro launcher real (build.gradle.kts + Dockerfile) con el data plane S3. El poc/ queda como referencia histórica.
  2. docker-compose.yml ampliado con MinIO + seed de buckets.
  3. Superficie de API recapturada (v4alpha) + requests S3 (requests/).
  4. Config R2 probada (endpoint EU, region auto, path-style, credenciales vía .env/vault) — snippet reutilizable.
  5. docs/dataspaces/fase-2-findings.md.

8. Definition of Done

  • Nuestro launcher (services/edc-service/) compila y arranca 2 conectores con el módulo S3.
  • Transfer S3→S3 contra MinIO verificado (objeto en consumer-inbox).
  • Transfer contra R2 real verificado (objeto en dataspace-inbox/ del bucket del lakehouse, endpoint EU).
  • Superficie de API recapturada en v4alpha con deltas documentados.
  • Credenciales R2 fuera de git; config documentada.
  • fase-2-findings.md cierra las sondas.

9. Riesgos

RiesgoMitigación
Desalineación de versiones core vs Technology-AwsPinear ambos a la misma release de EDC; verificar en el build.
v4alpha inestable (entidades v3/v4 colisionan; dataDestination fuera en transfer v4)Pin de versión; recapturar y documentar; mantener v3 donde v4 no esté listo.
Fuga de credenciales R2.env gitignored + inyección a contenedor + .gitignore revisado.
Paridad MinIO ↔ R2MinIO es buen stand-in, pero el paso 8 valida contra R2 real (no asumir).
Escribir en el bucket productivoUsar un prefijo scratch aislado (dataspace-inbox/), nunca rutas de datasets reales.
endpointOverride ignoradoConfirmar que va en el DataAddress (no solo config global); es un bug histórico ya resuelto pero a verificar.

10. Qué desbloquea

  • Fase 3 (edc-client.ts + executor): superficie de API v4 + patrón de asset S3 listos para el cliente Node.
  • Fase 4 (consumir → lakehouse): el aterrizaje en R2 ya probado → sólo falta registrar el snapshot Iceberg con withNativeControlPlane().
  • Fase 5 (zero-copy Lakekeeper): la fontanería S3/R2 y el gancho de data plane custom (visto en Fase 1) son la base del DataSource Iceberg.

11. Fuentes


Siguiente acción concreta al ejecutar: Paso 1 — crear el launcher services/edc-service/ con el módulo data-plane-aws-s3 y confirmar que arranca.