Ahora vamos a explicar cómo resolvemos esto desde Rust sin mencionar nickel, pero usando sus conceptos, que luego decidiremos que es la opción para manejar los tipados de forma externa y por qué, pero esto lo explicamos más tarde. En primer lugar necesitamos un esquema, es decir, necesitamos tener un patrón que determine que sirva de contenedor para lo que queremos declarar. En este caso, una forma de escribir, tener las opciones del menú para que se puedan elegir o no, unas estructuras en RASP, un esquema donde definimos que la cosa tiene, está asociada, cada elemento, cada ítem está asociado a un tipo, a un valor, un número, un string, un enum, etc. Junto a este elemento podemos asociarle unos valores por defecto, ¿vale? De manera que cuando no se proporciona un valor, utilice un valor por defecto. El tercer elemento es un constraint, una limitación dentro del tipo. Por ejemplo, en un puerto puedo usar un rango específico. En un plato puedo determinar tiene que ser un tipo de verdura de temporada, no solamente vegetal. Ahora introducimos dos elementos más. El primero es un validador, es decir, en mi declaración puedo introducir un validador, ¿vale? Es decir, dentro del rango de precios, dentro de lo que es posible en cocina, utilizo una función que valida que lo que estás introduciendo es un número entre un valor u otro, que ese número se corresponde con otro valor o con otro ítem dentro de la misma declaración. Los validadores los ponemos todos juntos y los podemos aplicar a diferentes declaraciones, estructuras o esquemas, al final son funciones con una entra y una salida. El segundo elemento es la composición, es decir, yo puedo sobreescribir o cambiar determinado conjunto o implementación de valores por defecto, puedo utilizar determinadas definiciones generales o globales, heredarlas y cambiarlas. Algo que podemos usar en Rust con funciones públicas o privadas y usando Traits. ¿Cuándo se valida todo esto? ¿Quién lo valida y con qué? Evidentemente no cuando el pedido ya llegado a cocina, sino antes de salir de la mesa. Incluso ya el comensal sabe lo que puede y lo que no puede hacer. Están las reglas preestablecidas. Falla en el tiempo de compilación, en el tiempo de construcción de la comanda o de la petición. Falla en el momento en el que se articula lo que se quiere. Esto es lo que elaboré en mi primera herramienta de despliegue en Rust CloudMandala en 2021. Usando el crate de Serde y parsing de valores cargados vía configfiles o environment values. ¿ Es correcto, es suficiente, es eficiente ? Sí, pero no. Sí en sí mismo, pero no, porque los mecanismos de despliegue cada vez se requiere que sean más ágiles, con iteración más rápida, los ciclos son cada vez más frecuentes. Si lo hago en RUST, en el momento en el que cambio una definición, un tipo, una declaración, una limitación, constraint o restricciones de valor, tengo que volver a recompilar el target de RUST y tengo que distribuir este target a la arquitectura que corresponda y en el lugar en el que tiene que estar para cumplir esta validación y seguir con los procesos. Es decir, esto me somete a que los ciclos de cambio y de adaptación son mucho más largos de lo que en sí demandan la CD/CI actual. En definitiva, es eficiente, pero no es suficientemente ágil. Entonces, ¿cómo puedo mantener estas funcionalidades sin Rust o de otra manera? Y para eso pensamos en usar lo que llamamos REPL, que son Read Evaluate Print Loop, es decir, programas que se ejecutan y que realizan determinado tipo de tareas de manera iterativa, en principio use Dhall (que está en Haskel) al final he utilizado KCL y finalmente Nickel que están escritos en Rust. Todos ellos permiten utilizar imports incluso desde repos git de bibliotecas de globales, de esquemas, utilizar defaults, utilizar constraints de valores mediante funciones con programación funcional, y además permiten composición y merge de valores. Su Su responsabilidad no va más allá de producir algo consistente y exportar en diferentes formatos de consumo, como JSON, Yaml o Toml. Esto significa que los targets que cargan estos ficheros, incluso tengo la carga directa desde ficheros NCL, que son los ficheros del tipo nickel, no tengan que hacer una segunda validación o una segunda consideración de los valores definidos en las declaraciones de forma exhaustiva. Estos REPL usan ficheros editables que puedo cambiar sobre la marcha de manera AGIL. ¿Es esto suficiente? No. ¿Por qué? Porque tener la declaración no significa que el camarero haya llevado la comanda a la cocina y que haya traído los platos a la mesa. Para eso necesito procedimientos, controles, ejecuciones, una serie de gestión de todos estos recorridos y tareas. ¿Lo puedo hacer esto en Rust? Sí, puedes hacer llamadas al sistema, puedes hacer controles, etc. Pero esto también tiene que ser ágil, es decir, tengo que poder cambiar los métodos y procedimientos con agilidad para reaccionar en el caso de que se caiga un plato por el camino, en el caso de que ocurra cualquier contingencia que cambie el plan de acción. Y para resolver esto, en vez de Rust, volvemos a usar scripts y volvemos a usar templates con engines tipo el crate de Tera. Nushel es una shell escrita en Rust, otro REPL que podemos usar pegamento o seguimiento o definir interacciones sobre la marcha, de una manera mucho más eficiente que un script normal, porque sigue los patrones de Rust de forma bastante intensiva. Es más, falla si no es capaz de cargar el código al inicio. Nushell tiene bucles "for par" para ejecutar acciones o llamadas en paralelo, por ejemplo, llamar a la CLI un proveedor para crear varias instancias a la vez en paralelo. Entonces, ¿para qué necesito RAS? Para nada. Entonces, ¿lo resuelvo todo con REPLs? No. Aquí es donde vuelvo de nuevo a RAS. ¿Por qué? Porque RAS ofrece una serie de capacidades de trabajo con multi-threads seguras que no están al alcance de la mayor parte de los lenguajes, scripts y demás que pueda utilizar en infraestructura. Eso hace que todas estas acciones con proveedores, ejecución de scripts que se han generado combinando valores declarados con plantillas, se realicen a través de un target en Rust que funciona como un orquestador. Él es capaz de ejecutar en paralelo, guardar valores seguros, recoger los retornos de resultado en las ejecuciones, realizar notificaciones de éxito o fracaso, reconducir o relanzar scripts que se predefinen para tareas alternativas e incluso rollback a estados previos, actua como un control-plane con gestion de estados y notificaciones. Es capaz de conectarse con vaults para obtener de forma los credenciales de acceso vía API a un proveedor, etc. Es suficiente para tener una maquinaria ¿Qué es la infraestructura? Es que la infraestructura no solamente son instancias, servidores, redes y demás, son también servicios que se declaran dentro de esa infraestructura, son configuraciones de estos servicios. Queremos que todo esté declarado y que sea elegible. No es primero este para crear instancias y luego aqué en Python con scripts shells para controlar la instalación de los paquetes, instalar la configuración de los servicios dentro de cada uno, bla, bla, bla. Como casi todo requiere de Infraestructura, de componentes que sean SOLID que puedan iteractuar con una declaración que se va adaptando a lo que va sucediendo ... Pero entonces, ¿dónde está la verdad? Mejor preguntémonos previamente qué es lo que entendemos por verdad. Si es algo inmutable, una especie de ley que fue declarada, escrita en el libro sagrado, ¿cómo vamos a darle credibilidad a esto? Y cómo se va a ir adaptando a lo que vamos necesitando en cambios sucesivos, ágiles, pero que mantienen una coherencia y una credibilidad a pesar de una dinámica continua. Rust no es muy puro ¿ tal vez porque admite y contempla modos de mutar a pesar de empezar con un enfoque inmutable ? (Aquí svg animado de los recursos de Rust para mutar y gestionar la inmutabilidad). Aquí mostramos la cosmología/arquitectura de Provisioning https://provisioning.systems/architecture-diagram.html