syntaxis/docs/provision/architecture-revision.md
Jesús Pérez 9cef9b8d57 refactor: consolidate configuration directories
Merge _configs/ into config/ for single configuration directory.
Update all path references.

Changes:
- Move _configs/* to config/
- Update .gitignore for new patterns
- No code references to _configs/ found

Impact: -1 root directory (layout_conventions.md compliance)
2025-12-26 18:36:23 +00:00

11 KiB

🔍 REVISIÓN CRÍTICA: KCL vs TOML - Ultra-análisis

Fecha: 2025-11-20 Clasificación: Análisis de decisión arquitectónica Estado: REVISANDO ANÁLISIS ANTERIOR


⚠️ PROBLEMA CON ANÁLISIS ANTERIOR

Mi recomendación previa de TOML fue incompleta y parcialmente incorrecta. Ignoró:

  1. KCL ya está integrado en provisioning con plugins desarrollados
  2. Ya tienes experiencia con KCL en tu stack
  3. KCL proporciona beneficios que TOML no tiene
  4. El problema real no es KCL vs TOML, es el acoplamiento a Rust

🔁 ANÁLISIS REVISADO: KCL Como Source of Truth

Ventajas de KCL que IGNORÉ:

✅ TIPADO & SCHEMA
   └─ Definiciones type-safe en tiempo de definición
   └─ Restricciones de esquema aplicadas automáticamente
   └─ Validación nativa sin código adicional

✅ MODULARIDAD
   └─ Herencia de definiciones
   └─ Sobrescritura de valores
   └─ Composición flexible
   └─ Reutilización de patrones

✅ CONVERSIÓN AUTOMÁTICA
   └─ KCL → YAML (para K8s)
   └─ KCL → JSON (para APIs)
   └─ KCL → HCL (para Terraform)
   └─ Transformación nativa

✅ SISTEMA NATIVO
   └─ Ya integrado en provisioning
   └─ Plugins de KCL desarrollados
   └─ Plugins de Tera desarrollados
   └─ Zero learning curve para tu equipo

✅ NO ES COMPLEJO
   └─ Curva de aprendizaje similar a TOML
   └─ Sintaxis más clara que código Rust
   └─ Documentación de provisioning aplica

🚨 PROBLEMA REAL: Acoplamiento a Rust

Mi análisis anterior confundió dos problemas:

FALSO PROBLEMA (que yo subrayé):
└─ "KCL es complejo"
└─ "TOML es más simple"
└─ "KCL es para infraestructura solo"

PROBLEMA REAL (que ignoré):
└─ ¿Cómo cambiar catalog sin recompilar Rust?
└─ ¿Qué pasa si la app no es Rust?
└─ ¿Cómo hace una app Node.js acceder al catalog?
└─ ¿Necesitamos compilar para cambios de definiciones?

💭 PREGUNTAS CRÍTICAS SIN RESPUESTA

Pregunta 1: ¿Cómo se integra el catalog actualmente?

Situación actual (inferida del código visto):

// En syntaxis (Rust)
let catalog = ServiceCatalog::from_file("services-catalog.toml")?;
catalog.generate_kubernetes("production")?;

Pregunta: ¿Cambiar el catalog requiere recompilar syntaxis?

Pregunta 2: ¿Qué pasa con apps no-Rust?

App Node.js quiere leer servicios
    ↓
¿Cómo accede al catalog?
    ├─ Vía HTTP API? (necesita endpoint)
    ├─ Vía archivo TOML/KCL? (necesita copiar)
    ├─ Vía provisioning? (tight coupling)
    └─ Vía qué mecanismo?

Pregunta 3: ¿Dónde vive el source of truth?

Opciones:
1. En provisioning (KCL)
   ├─ Ventaja: Centralizado, nativo
   ├─ Desventaja: Apps no-KCL ¿cómo lo leen?

2. En syntaxis (TOML)
   ├─ Ventaja: Agnóstico del lenguaje
   ├─ Desventaja: Pierdes beneficios de KCL

3. En ambos (SYNC)
   ├─ Ventaja: Cada uno en su contexto
   ├─ Desventaja: Mantener sync es complejo

🏗️ ARQUITECTURA PROPUESTA REVISADA

Opción A: KCL as Source of Truth (RECOMENDADO)

┌─────────────────────────────────┐
│  KCL Service Definitions        │
│  (provisioning directory)       │
│                                 │
│  services.k:                    │
│  ├─ service "api" = { ... }     │
│  ├─ service "db" = { ... }      │
│  ├─ pattern "prod" = { ... }    │
│  └─ group "backend" = { ... }   │
└─────────────────────────────────┘
         ↓ (API/File)
    ┌─────────────┐
    │ KCL Runtime │
    └─────────────┘
      ↙  ↓  ↘
    ┌─────────┬─────────┬─────────┐
    ↓         ↓         ↓         ↓
   YAML      JSON     HCL    Rust Struct
  (K8s)     (API)   (TF)    (Direct use)
    ↓         ↓         ↓         ↓
   K8s      API      Terraform  syntaxis
  Deploy   Clients   Deploy    Compile

VENTAJAS:
✅ Source of truth único
✅ No requiere recompilación
✅ KCL proporciona validación nativa
✅ Apps pueden leer JSON/YAML outputs
✅ Agnóstico del lenguaje (outputs)
✅ Aprovecha inversión en KCL existente

DESVENTAJAS:
❌ Apps deben parsear JSON/YAML (no idiomatic)
❌ Requiere KCL runtime disponible
❌ Coupling con provisioning

Opción B: KCL Input → Multi-Output Layer

KCL Definition
    ↓
KCL Compiler (en provisioning)
    ↓
JSON Intermediate (API Endpoint)
    ├─→ Rust (ServiceRegistry trait)
    ├─→ Node.js (npm package)
    ├─→ Python (pip package)
    ├─→ Go (go package)
    └─→ Java (maven artifact)

VENTAJAS:
✅ Source único (KCL)
✅ Multiple language bindings
✅ No requiere recompilación
✅ Cada lenguaje accede idiomatically

DESVENTAJAS:
❌ Más complejo de mantener
❌ N idiomas = N implementaciones
❌ Sincronización de versiones

Opción C: Híbrida - KCL + REST API

Provisioning (KCL source)
    ↓
/api/v1/services (REST API)
├─ GET /services → {[Service]}
├─ GET /services/{name} → {Service}
├─ GET /patterns → {[Pattern]}
├─ GET /validate → {ValidationResult}
└─ POST /generate → {code}

VENTAJAS:
✅ Source único (KCL)
✅ Agnóstico del lenguaje
✅ Fácil integración
✅ No requiere compilación
✅ Versionable

DESVENTAJAS:
❌ Runtime dependency (API server)
❌ Latencia de red
❌ Overhead de HTTP

🎯 ANÁLISIS: ¿Cuál es mejor?

Para TU caso específico:

CONTEXTO:
├─ Ya usas KCL en provisioning ✅
├─ Tienes plugins KCL desarrollados ✅
├─ Tienes plugins Tera desarrollados ✅
├─ Equipo conoce KCL ✅
├─ Necesitas agnóstico del lenguaje ⚠️
└─ Quieres evitar recompilación ⚠️

RECOMENDACIÓN: Opción C (Híbrida)
├─ KCL como source de truth (aprovecha inversión)
├─ REST API como interfaz (agnóstico)
└─ Evita recompilación (cambia KCL, API refleja)

🔧 Implementación: KCL + REST API

Paso 1: Ampliar provisioning

// en provisioning/src/main.rs
use axum::{Router, Json};

#[tokio::main]
async fn main() {
    // Cargar KCL definitions
    let catalog = KclServiceCatalog::load("services.k")?;

    // Exponer como REST API
    let routes = Router::new()
        .route("/api/v1/services", get(list_services))
        .route("/api/v1/services/:name", get(get_service))
        .route("/api/v1/patterns", get(list_patterns))
        .route("/api/v1/validate", post(validate))
        .route("/api/v1/generate/:format/:pattern", post(generate));

    axum::Server::bind(&"127.0.0.1:8080".parse()?)
        .serve(routes.into_make_service())
        .await?;
}

async fn list_services(
    State(catalog): State<Arc<KclServiceCatalog>>,
) -> Json<Vec<Service>> {
    Json(catalog.services())
}

Paso 2: Multi-language SDKs

// clients/javascript/service-registry.js
class ServiceRegistry {
    constructor(apiUrl = "http://127.0.0.1:8080") {
        this.apiUrl = apiUrl;
    }

    async getServices() {
        const res = await fetch(`${this.apiUrl}/api/v1/services`);
        return res.json();
    }

    async getService(name) {
        const res = await fetch(`${this.apiUrl}/api/v1/services/${name}`);
        return res.json();
    }

    async generate(format, pattern) {
        const res = await fetch(
            `${this.apiUrl}/api/v1/generate/${format}/${pattern}`,
            { method: "POST" }
        );
        return res.text();
    }
}

module.exports = ServiceRegistry;
# clients/python/service_registry.py
import requests

class ServiceRegistry:
    def __init__(self, api_url="http://127.0.0.1:8080"):
        self.api_url = api_url

    def get_services(self):
        res = requests.get(f"{self.api_url}/api/v1/services")
        return res.json()

    def get_service(self, name):
        res = requests.get(f"{self.api_url}/api/v1/services/{name}")
        return res.json()

    def generate(self, format, pattern):
        res = requests.post(
            f"{self.api_url}/api/v1/generate/{format}/{pattern}"
        )
        return res.text()

Paso 3: syntaxis usa el API

// En syntaxis, cambiar de:
let catalog = ServiceCatalog::from_file("services-catalog.toml")?;

// A:
let client = ServiceRegistryClient::new("http://provisioning:8080");
let catalog = client.get_services().await?;

📊 Comparativa Revisada

Aspecto TOML (Original) KCL (Revisado) KCL+API (Propuesto)
Type-safe
Schema validation Parcial Nativo Nativo
Modular/Herencia
Multi-lenguaje
Sin recompilación
Usa inversión KCL
Plugins existentes
Complejidad Baja Media Media
Coupling Bajo Alto Bajo

🎯 CONCLUSIÓN REVISADA

Mi error anterior:

ASUMÍ: "KCL es complejo, TOML es simple"
REALIDAD: Tú ya usas KCL, ya tienes plugins,
          el problema es ACOPLAMIENTO, no complejidad

La solución correcta:

✅ Usa KCL como source of truth (aprovecha inversión)
✅ Expone como REST API (agnóstico del lenguaje)
✅ Cada app accede idiomatically (Python, Node, Rust, etc)
✅ Sin recompilación (cambias KCL, API refleja)
✅ Mantiene beneficios de KCL (tipado, validación, modularidad)
✅ Agrega beneficios de agnóstico del lenguaje

🚀 Próximos Pasos de Análisis

Pregunta 1: ¿Cuál es el volumen de cambios?

  • ¿Cuán frecuentemente cambias el catálogo?
  • ¿Quién hace los cambios? (DevOps, Developers, Both?)
  • ¿Impacta en usuarios finales?

Pregunta 2: ¿Cuáles son los consumidores?

  • syntaxis (Rust)
  • ¿Otros servicios?
  • ¿Herramientas de terceros?
  • ¿Dashboards?

Pregunta 3: ¿Cuál es el flujo actual?

  • ¿Cómo se despliegan cambios de provisioning?
  • ¿Hay CI/CD para KCL definitions?
  • ¿Se compilan antes de deployar?

PREGUNTAS PARA TI

Para hacer un análisis completo, necesito entender:

  1. ¿Por qué no estaban usando KCL directamente desde el inicio?

    • ¿Había una razón técnica?
    • ¿Fue una decisión de simplicidad?
    • ¿O Rust era el único consumidor?
  2. ¿Cómo funciona provisioning actualmente con KCL?

    • ¿Dónde están los archivos KCL?
    • ¿Cómo se cargan y procesan?
    • ¿Hay conversión a otros formatos ya?
  3. ¿Quiénes son los consumidores del catálogo?

    • syntaxis
    • ¿Otros proyectos?
    • ¿Herramientas de DevOps?
    • ¿Sistemas de monitoring?
  4. ¿Cuál es la frecuencia de cambios?

    • ¿Cuándo se cambia el catálogo?
    • ¿Quién lo cambia?
    • ¿Qué tan rápido necesita reflejarse?

Este es un tema que requiere más contexto de tu arquitectura actual para dar la recomendación correcta.