{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/rust-wasm-component-model-production","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production","title":"Rust + WASM Component Model en Producción: Microservicios Políglotas Que Realmente Funcionan","description":"Construyendo microservicios políglotas en producción con Rust, el WASM Component Model y interfaces WIT. Composición cross-language, workflows con wasm-tools, y lecciones duras de correr esto a escala.","language":"es","datePublished":"2025-10-08","dateModified":null,"contentFormat":"text/markdown","content":"WebAssembly\n\n# Rust + WASM Component Model en Producción: Microservicios Políglotas Que Realmente Funcionan\n\n2025-10-088 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/rust-wasm-component-model-production>)\n\n## Definición del Component Model\n\nCortemos con el marketing. Un WASM Component es:\n\n1. Un binario ` .wasm ` que sigue la spec del Component Model (no un módulo wasm \"core\")\n2. Declara **imports** tipados (lo que necesita) y **exports** (lo que provee)\n3. Estos tipos se definen en archivos **WIT** (WebAssembly Interface Type)\n4. Los componentes se pueden **componer**, conectar en sus boundaries de import/export\n5. Cada componente tiene su propia memoria lineal (sandboxeado por default)\n\nAcá va una definición WIT mínima para un componente de autenticación:\n\n```wit\n// auth.wit\npackage myorg:auth@1.0.0;\n\ninterface authenticate {\n record auth-request {\n token: string,\n required-scopes: list<string>,\n }\n\n record auth-result {\n authenticated: bool,\n user-id: option<string>,\n granted-scopes: list<string>,\n error: option<string>,\n }\n\n verify: func(req: auth-request) -> auth-result;\n}\n\nworld auth-component {\n export authenticate;\n}\n```\n\nY la implementación en Rust:\n\n```rust\n// src/lib.rs\nwit_bindgen::generate!({\n world: \"auth-component\",\n path: \"../wit/auth.wit\",\n});\n\nstruct AuthComponent;\n\nimpl Guest for AuthComponent {\n fn verify(req: AuthRequest) -> AuthResult {\n let claims = match decode_jwt(&req.token) {\n Ok(c) => c,\n Err(e) => return AuthResult {\n authenticated: false,\n user_id: None,\n granted_scopes: vec![],\n error: Some(format!(\"JWT decode failed: {e}\")),\n },\n };\n\n let granted: Vec<String> = req.required_scopes\n .iter()\n .filter(|s| claims.scopes.contains(s))\n .cloned()\n .collect();\n\n let all_granted = granted.len() == req.required_scopes.len();\n\n AuthResult {\n authenticated: all_granted,\n user_id: Some(claims.sub.clone()),\n granted_scopes: granted,\n error: if all_granted { None } else {\n Some(\"Insufficient scopes\".into())\n },\n }\n }\n}\n\nexport!(AuthComponent);\n```\n\nLa macro ` wit_bindgen::generate! ` lee el archivo WIT y genera todas las definiciones de tipos y el código glue. Se implementa el trait, se exporta el struct, lo buildeas:\n\n```bash\ncargo component build --release\n```\n\nEso produce un componente ` .wasm ` (no un módulo core) que cualquier runtime del Component Model puede cargar y llamar.\n\n## Composición Cross-Language: El Poder Real\n\nAcá resulta interesante. Supongamos que tu componente de auth está en Rust, pero tu API gateway está en Go, y tu lógica de negocio está en Python (usando componentize-py). Cada uno se compila a un componente WASM separado.\n\nPrimero, el WIT del componente gateway en Go:\n\n```wit\n// gateway.wit\npackage myorg:gateway@1.0.0;\n\ninterface handle-request {\n record http-request {\n method: string,\n path: string,\n headers: list<tuple<string, string>>,\n body: option<list<u8>>,\n }\n\n record http-response {\n status: u16,\n headers: list<tuple<string, string>>,\n body: list<u8>,\n }\n\n handle: func(req: http-request) -> http-response;\n}\n\nworld gateway-component {\n import myorg:auth/authenticate@1.0.0;\n import myorg:bizlogic/process@1.0.0;\n export handle-request;\n}\n```\n\nLa implementación en Go importa la interfaz de auth y la llama como una función local, sin HTTP, sin serialización:\n\n```go\npackage main\n\nimport (\n \"myorg/gateway/gen\"\n auth \"myorg/auth/gen\"\n bizlogic \"myorg/bizlogic/gen\"\n)\n\ntype Gateway struct{}\n\nfunc (g *Gateway) Handle(req gen.HttpRequest) gen.HttpResponse {\n token := extractBearerToken(req.Headers)\n\n // Llamar al componente auth de Rust, mismo proceso, sin red\n authResult := auth.Verify(auth.AuthRequest{\n Token: token,\n RequiredScopes: []string{\"read:users\"},\n })\n\n if !authResult.Authenticated {\n return gen.HttpResponse{\n Status: 401,\n Body: []byte(`{\"error\": \"unauthorized\"}`),\n }\n }\n\n // Llamar al componente de lógica de negocio en Python\n result := bizlogic.Process(bizlogic.ProcessRequest{\n UserId: *authResult.UserId,\n Action: \"list_users\",\n Payload: req.Body,\n })\n\n return gen.HttpResponse{\n Status: 200,\n Headers: []gen.Header{{\"content-type\", \"application/json\"}},\n Body: result.Data,\n }\n}\n```\n\nAhora los componemos:\n\n```bash\n# Buildear cada componente\ncargo component build --release -p auth-component\ntinygo build -target=wasip2 -o gateway.wasm ./gateway\ncomponentize-py -d ./wit -w bizlogic-component ./bizlogic.py -o bizlogic.wasm\n\n# Componer en un solo componente linkeado\nwasm-tools compose \\\n --definitions auth-component.wasm \\\n --definitions bizlogic.wasm \\\n -o composed-service.wasm \\\n gateway.wasm\n```\n\nEl ` composed-service.wasm ` es un solo binario conteniendo los tres componentes, con sus imports/exports conectados. Lo es posible correr en wasmtime, [Cloudflare](<https://cloudflare.com/>) Workers, Fermyon Spin, o cualquier runtime del Component Model.\n\n## El Pipeline de Build en Detalle\n\nHacer funcionar esto en CI toma más esfuerzo del esperado. Acá está un Makefile que realmente funciona:\n\n```makefile\nWIT_DIR := ./wit\nBUILD_DIR := ./build\nWASI_ADAPTER := ./adapters/wasi_snapshot_preview1.reactor.wasm\n\n.PHONY: all clean validate compose\n\nall: validate compose\n\nvalidate:\n\twasm-tools component wit $(WIT_DIR) --check\n\n$(BUILD_DIR)/auth.wasm: auth/src/lib.rs $(WIT_DIR)/auth.wit\n\tcd auth && cargo component build --release\n\tcp auth/target/wasm32-wasip2/release/auth_component.wasm $@\n\twasm-opt -Oz $@ -o $@\n\n$(BUILD_DIR)/gateway.wasm: gateway/main.go $(WIT_DIR)/gateway.wit\n\tcd gateway && tinygo build -target=wasip2 -o ../$@\n\twasm-tools component new $@ --adapt $(WASI_ADAPTER) -o $@\n\n$(BUILD_DIR)/bizlogic.wasm: bizlogic/app.py $(WIT_DIR)/bizlogic.wit\n\tcomponentize-py -d $(WIT_DIR) -w bizlogic-component bizlogic/app.py -o $@\n\ncompose: $(BUILD_DIR)/auth.wasm $(BUILD_DIR)/gateway.wasm $(BUILD_DIR)/bizlogic.wasm\n\twasm-tools compose \\\n\t\t-d $(BUILD_DIR)/auth.wasm \\\n\t\t-d $(BUILD_DIR)/bizlogic.wasm \\\n\t\t-o $(BUILD_DIR)/composed-service.wasm \\\n\t\t$(BUILD_DIR)/gateway.wasm\n\t@echo \"Binario compuesto: $$(du -h $(BUILD_DIR)/composed-service.wasm | cut -f1)\"\n\nclean:\n\trm -rf $(BUILD_DIR)/*\n```\n\nDos lecciones importantes:\n\n1. **La validación de WIT tiene que ir primero.** Un typo en un archivo WIT produce errores inescrutables downstream. ` wasm-tools component wit --check ` los atrapa temprano.\n2. **El adapter WASI es sensible a la versión.** TinyGo, el target ` wasm32-wasip2 ` de Rust, y componentize-py cada uno espera una versión específica de WASI preview. El binario del adapter se debe pinar en el repo y referenciarlo explícitamente.\n\n## Rendimiento: Análisis de Compromisos\n\nAcá van números reales de un deployment de producción. El setup tiene 12 componentes WASM (mezcla de Rust, Go y Python) compuestos en 3 servicios, corriendo en un orquestador custom basado en wasmtime 18 en bare metal.\n\n**Overhead de llamada entre componentes:**\n\n| Tamaño del payload | Latencia | Notas |\n| --- | --- | --- |\n| Vacío (sin args) | 0.8μs | Costo base de dispatch |\n| Struct de 1KB | 2.1μs | Incluye serialización ABI canónica |\n| Blob de 64KB | 18μs | Dominado por memcpy entre memorias lineales |\n| Blob de 1MB | 156μs | Lineal con el tamaño, como se espera |\n\nPara contexto, una llamada gRPC por loopback es ~50μs para un mensaje vacío, y ~200μs para 1KB. Así que las llamadas entre componentes son ~25x más rápidas que gRPC para payloads chicos. La brecha se achica con payloads grandes porque ambos están bottlenecked en la copia de datos.\n\n**Overhead de CPU vs nativo (benchmark compute-bound, sin I/O):**\n\n| Lenguaje del componente | vs nativo | Notas |\n| --- | --- | --- |\n| Rust → WASM | 1.15x | Overhead mínimo, bounds checks |\n| Go → WASM (TinyGo) | 1.45x | El GC de TinyGo es más simple, faltan algunas opts |\n| Python → WASM | 3.2x | componentize-py incluye intérprete CPython |\n\nEl número de Python se ve feo, pero recordar: este componente Python corre en el mismo proceso, sin overhead de container, sin cold start, y con sandboxing completo. Si la alternativa es un container Python con 800ms de cold start, 3.2x de overhead de CPU en una computación de 5ms es un trade fantástico.\n\n**Huella de memoria:**\n\n| Deployment | Memoria por instancia |\n| --- | --- |\n| 12 containers Docker (1 por componente) | ~3.2GB total |\n| 3 servicios WASM compuestos (4 componentes c/u) | ~420MB total |\n| Un solo WASM compuesto (los 12 componentes) | ~180MB |\n\nLos ahorros de memoria son dramáticos porque los componentes WASM comparten el runtime (wasmtime) y solo alocan su propia memoria lineal. La memoria lineal de un componente Rust empieza en 64KB y crece bajo demanda.\n\n## La Historia del Sandboxing\n\nEsta es la parte que convence a los equipos de seguridad. Cada componente WASM:\n\n- No puede acceder al filesystem a menos que se le dé explícitamente una capability ` wasi:filesystem `\n- No puede hacer llamadas de red a menos que se le dé ` wasi:sockets `\n- No puede leer la memoria de otro componente (memorias lineales separadas)\n- No puede ejecutar syscalls arbitrarios (modelo de capabilities de WASI)\n\nConsideremos un vendor tercero que provee un componente de \"enrichment de datos\". En el mundo de containers, se necesitaría correrlo en un namespace de red separado, configurar políticas AppArmor/SELinux, armar filtrado de egress, etc. Con WASM, el componente se puede componer en el servicio con cero capabilities, puede computar sobre los datos que se le pasan y devolver resultados. Eso nomás. Si el código intenta llamar a casa, la llamada falla en el boundary de WASI, no en un firewall de red.\n\n```bash\n# Componer componente del vendor SIN capabilities WASI\nwasm-tools compose \\\n --definitions vendor-enrichment.wasm \\\n --definitions our-service.wasm \\\n -o final.wasm \\\n orchestrator.wasm\n```\n\n## Errores Comunes a Evitar\n\n**Error 1: Poner parsing de JSON en el boundary del componente.** Un primer diseño común tiene componentes intercambiando strings JSON. Esto significaba que cada llamada cross-componente pagaba por serialize + deserialize + dos alocaciones de string. Cambiar a tipos ` record ` de WIT con el ABI canónico eliminó esto completamente.\n\n**Error 2: Un componente gigante en vez de composición.** Un enfoque inicial común compila todo en un solo módulo WASM (no componente). Funcionaba pero perdía todos los beneficios de sandboxing y hacía imposible actualizar sin redeployar todo. La composición del Component Model es el punto, te deja actualizar un componente sin tocar los otros.\n\n**Error 3: Ignorar la versión del adapter.** Es posible perder tres días debuggeando un segfault que resultó ser un mismatch entre el adapter WASI preview 1 y un componente buildeado para preview 2. Fijar las versiones de tu adapter. En serio.\n\n## Cuándo Usar Esto (y Cuándo No)\n\nUtilizar el WASM Component Model cuando:\n\n- Se necesitan componer código de múltiples lenguajes en un solo proceso\n- El aislamiento de seguridad entre componentes es importante\n- Querés alta densidad de deployment (muchos servicios, memoria limitada)\n- Tus servicios son compute-heavy con intercambios de datos chicos\n\nNo lo uses cuando:\n\n- Todo tu código es un solo lenguaje (utilizar el tooling nativo de ese lenguaje)\n- Se necesitan intercambiar datasets grandes entre componentes (el overhead de copia se acumula)\n- Tu equipo no se siente cómodo con la complejidad del build toolchain\n- Se necesitan herramientas maduras de debugging/profiling (están mejorando, pero no llegaron todavía)\n\nEl Component Model no está reemplazando Kubernetes ni Docker. Es una herramienta nueva para una clase específica de problemas, y para esos problemas, es notablemente efectivo.\n\n## Preguntas frecuentes\n\n### ¿El WASM Component Model está listo para producción?\n\nPara case es de uso específicos, sí. Se vienen corriendo componentes WASM de Rust en Cloudflare Workers y en un orquestador custom basado en wasmtime desde Q3 2025. La spec del Component Model llegó a 1.0 a mediados de 2025, y herramientas como wasm-tools 1.2+ y cargo-component 0.16+ son estables. La brecha está en la madurez del ecosistema, la mayoría de las librerías todavía no exponen interfaces WIT.\n\n### ¿Cómo funciona realmente la composición cross-language?\n\nCada componente es un binario .wasm autocontenido con imports y exports tipados definidos en WIT. Un componente Go puede importar una función exportada por un componente Rust, y el runtime (wasmtime, wamr, etc.) maneja la traducción ABI. La restricción clave es que los componentes no comparten memoria, todos los datos que cruzan boundaries se copian a través del ABI canónico.\n\n### ¿Cuál es el overhead de rendimiento comparado con microservicios nativos?\n\nPara lógica CPU-bound, los benchmarks muestran 1.2-1.8x de overhead vs Rust nativo, mayormente por bounds checking y llamadas indirectas. Para servicios I/O-bound (que es la mayoría del código real), el overhead es despreciable porque igual se está esperando red/disco. La ganancia está en la densidad de deployment, 40 componentes WASM pueden correr en la huella de memoria de 8 containers nativos.\n\n## Tips\n\n1. **Fijar tus versiones de WIT en un git submodule:** Las interfaces WIT son tus contratos de API. Ponelas en un repo dedicado y referenciá como submodule. Cuando corrés ` wasm-tools component new `, pasá ` --adapt wasi_snapshot_preview1=adapter.wasm ` para pinar la versión del adapter WASI también. El drift de versiones entre WIT y adapter causa mismatches de ABI silenciosos.\n2. **Utilizar ` cargo component build --release ` con LTO:** El build debug default produce archivos .wasm 5-10x más grandes. Agregar ` lto = true ` y ` opt-level = 's' ` en tu perfil release del Cargo.toml, combinado con ` wasm-opt -Oz `, típicamente lleva un componente de 2MB a 180KB.\n3. **Evitar ` String ` en los límites de interfaz WIT:** Cada string que cruza el límite del componente requiere una alocación + copia porque los componentes tienen memorias lineales separadas. Utilizar ` list<u8> ` con encoding conocido, o mejor, definí tipos ` record ` con campos de tamaño fijo. Los datos de producción muestran una reducción de latencia del 40% en una interfaz pesada de JSON al cambiar a records estructurados.\n4. **Probar el linking de componentes localmente con ` wasmtime compose `:** Antes de deployar, ejecutar ` wasm-tools compose -d deps/ -o linked.wasm your-component.wasm ` para atrapar mismatches de interfaz en build time. Los mensajes de error del linking en runtime son crípticos; la herramienta compose te da diagnósticos claros.\n\n- rust\n- webassembly\n- component-model\n- wit\n- wasm-tools\n- microservicios\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"Rust + WASM Component Model en Producción: Microservicios Políglotas Que Realmente Funcionan","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#title"},{"level":2,"text":"Definición del Component Model","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#definicin-del-component-model"},{"level":2,"text":"Composición Cross-Language: El Poder Real","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#composicin-cross-language-el-poder-real"},{"level":2,"text":"El Pipeline de Build en Detalle","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#el-pipeline-de-build-en-detalle"},{"level":2,"text":"Rendimiento: Análisis de Compromisos","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#rendimiento-anlisis-de-compromisos"},{"level":2,"text":"La Historia del Sandboxing","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#la-historia-del-sandboxing"},{"level":2,"text":"Errores Comunes a Evitar","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#errores-comunes-a-evitar"},{"level":2,"text":"Cuándo Usar Esto (y Cuándo No)","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#cundo-usar-esto-y-cundo-no"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#questions"},{"level":3,"text":"¿El WASM Component Model está listo para producción?","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#question-1"},{"level":3,"text":"¿Cómo funciona realmente la composición cross-language?","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#question-2"},{"level":3,"text":"¿Cuál es el overhead de rendimiento comparado con microservicios nativos?","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#article-tips"}],"sections":[{"heading":"Rust + WASM Component Model en Producción: Microservicios Políglotas Que Realmente Funcionan","level":1,"url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production","content":"WebAssembly"},{"heading":"Rust + WASM Component Model en Producción: Microservicios Políglotas Que Realmente Funcionan","level":1,"url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#title","content":"2025-10-088 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/rust-wasm-component-model-production>)"},{"heading":"Definición del Component Model","level":2,"url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#definicin-del-component-model","content":"Cortemos con el marketing. Un WASM Component es:\n\n1. Un binario ` .wasm ` que sigue la spec del Component Model (no un módulo wasm \"core\")\n2. Declara **imports** tipados (lo que necesita) y **exports** (lo que provee)\n3. Estos tipos se definen en archivos **WIT** (WebAssembly Interface Type)\n4. Los componentes se pueden **componer**, conectar en sus boundaries de import/export\n5. Cada componente tiene su propia memoria lineal (sandboxeado por default)\n\nAcá va una definición WIT mínima para un componente de autenticación:\n\n```wit\n// auth.wit\npackage myorg:auth@1.0.0;\n\ninterface authenticate {\n record auth-request {\n token: string,\n required-scopes: list<string>,\n }\n\n record auth-result {\n authenticated: bool,\n user-id: option<string>,\n granted-scopes: list<string>,\n error: option<string>,\n }\n\n verify: func(req: auth-request) -> auth-result;\n}\n\nworld auth-component {\n export authenticate;\n}\n```\n\nY la implementación en Rust:\n\n```rust\n// src/lib.rs\nwit_bindgen::generate!({\n world: \"auth-component\",\n path: \"../wit/auth.wit\",\n});\n\nstruct AuthComponent;\n\nimpl Guest for AuthComponent {\n fn verify(req: AuthRequest) -> AuthResult {\n let claims = match decode_jwt(&req.token) {\n Ok(c) => c,\n Err(e) => return AuthResult {\n authenticated: false,\n user_id: None,\n granted_scopes: vec![],\n error: Some(format!(\"JWT decode failed: {e}\")),\n },\n };\n\n let granted: Vec<String> = req.required_scopes\n .iter()\n .filter(|s| claims.scopes.contains(s))\n .cloned()\n .collect();\n\n let all_granted = granted.len() == req.required_scopes.len();\n\n AuthResult {\n authenticated: all_granted,\n user_id: Some(claims.sub.clone()),\n granted_scopes: granted,\n error: if all_granted { None } else {\n Some(\"Insufficient scopes\".into())\n },\n }\n }\n}\n\nexport!(AuthComponent);\n```\n\nLa macro ` wit_bindgen::generate! ` lee el archivo WIT y genera todas las definiciones de tipos y el código glue. Se implementa el trait, se exporta el struct, lo buildeas:\n\n```bash\ncargo component build --release\n```\n\nEso produce un componente ` .wasm ` (no un módulo core) que cualquier runtime del Component Model puede cargar y llamar."},{"heading":"Composición Cross-Language: El Poder Real","level":2,"url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#composicin-cross-language-el-poder-real","content":"Acá resulta interesante. Supongamos que tu componente de auth está en Rust, pero tu API gateway está en Go, y tu lógica de negocio está en Python (usando componentize-py). Cada uno se compila a un componente WASM separado.\n\nPrimero, el WIT del componente gateway en Go:\n\n```wit\n// gateway.wit\npackage myorg:gateway@1.0.0;\n\ninterface handle-request {\n record http-request {\n method: string,\n path: string,\n headers: list<tuple<string, string>>,\n body: option<list<u8>>,\n }\n\n record http-response {\n status: u16,\n headers: list<tuple<string, string>>,\n body: list<u8>,\n }\n\n handle: func(req: http-request) -> http-response;\n}\n\nworld gateway-component {\n import myorg:auth/authenticate@1.0.0;\n import myorg:bizlogic/process@1.0.0;\n export handle-request;\n}\n```\n\nLa implementación en Go importa la interfaz de auth y la llama como una función local, sin HTTP, sin serialización:\n\n```go\npackage main\n\nimport (\n \"myorg/gateway/gen\"\n auth \"myorg/auth/gen\"\n bizlogic \"myorg/bizlogic/gen\"\n)\n\ntype Gateway struct{}\n\nfunc (g *Gateway) Handle(req gen.HttpRequest) gen.HttpResponse {\n token := extractBearerToken(req.Headers)\n\n // Llamar al componente auth de Rust, mismo proceso, sin red\n authResult := auth.Verify(auth.AuthRequest{\n Token: token,\n RequiredScopes: []string{\"read:users\"},\n })\n\n if !authResult.Authenticated {\n return gen.HttpResponse{\n Status: 401,\n Body: []byte(`{\"error\": \"unauthorized\"}`),\n }\n }\n\n // Llamar al componente de lógica de negocio en Python\n result := bizlogic.Process(bizlogic.ProcessRequest{\n UserId: *authResult.UserId,\n Action: \"list_users\",\n Payload: req.Body,\n })\n\n return gen.HttpResponse{\n Status: 200,\n Headers: []gen.Header{{\"content-type\", \"application/json\"}},\n Body: result.Data,\n }\n}\n```\n\nAhora los componemos:\n\n```bash\n# Buildear cada componente\ncargo component build --release -p auth-component\ntinygo build -target=wasip2 -o gateway.wasm ./gateway\ncomponentize-py -d ./wit -w bizlogic-component ./bizlogic.py -o bizlogic.wasm\n\n# Componer en un solo componente linkeado\nwasm-tools compose \\\n --definitions auth-component.wasm \\\n --definitions bizlogic.wasm \\\n -o composed-service.wasm \\\n gateway.wasm\n```\n\nEl ` composed-service.wasm ` es un solo binario conteniendo los tres componentes, con sus imports/exports conectados. Lo es posible correr en wasmtime, [Cloudflare](<https://cloudflare.com/>) Workers, Fermyon Spin, o cualquier runtime del Component Model."},{"heading":"El Pipeline de Build en Detalle","level":2,"url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#el-pipeline-de-build-en-detalle","content":"Hacer funcionar esto en CI toma más esfuerzo del esperado. Acá está un Makefile que realmente funciona:\n\n```makefile\nWIT_DIR := ./wit\nBUILD_DIR := ./build\nWASI_ADAPTER := ./adapters/wasi_snapshot_preview1.reactor.wasm\n\n.PHONY: all clean validate compose\n\nall: validate compose\n\nvalidate:\n\twasm-tools component wit $(WIT_DIR) --check\n\n$(BUILD_DIR)/auth.wasm: auth/src/lib.rs $(WIT_DIR)/auth.wit\n\tcd auth && cargo component build --release\n\tcp auth/target/wasm32-wasip2/release/auth_component.wasm $@\n\twasm-opt -Oz $@ -o $@\n\n$(BUILD_DIR)/gateway.wasm: gateway/main.go $(WIT_DIR)/gateway.wit\n\tcd gateway && tinygo build -target=wasip2 -o ../$@\n\twasm-tools component new $@ --adapt $(WASI_ADAPTER) -o $@\n\n$(BUILD_DIR)/bizlogic.wasm: bizlogic/app.py $(WIT_DIR)/bizlogic.wit\n\tcomponentize-py -d $(WIT_DIR) -w bizlogic-component bizlogic/app.py -o $@\n\ncompose: $(BUILD_DIR)/auth.wasm $(BUILD_DIR)/gateway.wasm $(BUILD_DIR)/bizlogic.wasm\n\twasm-tools compose \\\n\t\t-d $(BUILD_DIR)/auth.wasm \\\n\t\t-d $(BUILD_DIR)/bizlogic.wasm \\\n\t\t-o $(BUILD_DIR)/composed-service.wasm \\\n\t\t$(BUILD_DIR)/gateway.wasm\n\t@echo \"Binario compuesto: $$(du -h $(BUILD_DIR)/composed-service.wasm | cut -f1)\"\n\nclean:\n\trm -rf $(BUILD_DIR)/*\n```\n\nDos lecciones importantes:\n\n1. **La validación de WIT tiene que ir primero.** Un typo en un archivo WIT produce errores inescrutables downstream. ` wasm-tools component wit --check ` los atrapa temprano.\n2. **El adapter WASI es sensible a la versión.** TinyGo, el target ` wasm32-wasip2 ` de Rust, y componentize-py cada uno espera una versión específica de WASI preview. El binario del adapter se debe pinar en el repo y referenciarlo explícitamente."},{"heading":"Rendimiento: Análisis de Compromisos","level":2,"url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#rendimiento-anlisis-de-compromisos","content":"Acá van números reales de un deployment de producción. El setup tiene 12 componentes WASM (mezcla de Rust, Go y Python) compuestos en 3 servicios, corriendo en un orquestador custom basado en wasmtime 18 en bare metal.\n\n**Overhead de llamada entre componentes:**\n\n| Tamaño del payload | Latencia | Notas |\n| --- | --- | --- |\n| Vacío (sin args) | 0.8μs | Costo base de dispatch |\n| Struct de 1KB | 2.1μs | Incluye serialización ABI canónica |\n| Blob de 64KB | 18μs | Dominado por memcpy entre memorias lineales |\n| Blob de 1MB | 156μs | Lineal con el tamaño, como se espera |\n\nPara contexto, una llamada gRPC por loopback es ~50μs para un mensaje vacío, y ~200μs para 1KB. Así que las llamadas entre componentes son ~25x más rápidas que gRPC para payloads chicos. La brecha se achica con payloads grandes porque ambos están bottlenecked en la copia de datos.\n\n**Overhead de CPU vs nativo (benchmark compute-bound, sin I/O):**\n\n| Lenguaje del componente | vs nativo | Notas |\n| --- | --- | --- |\n| Rust → WASM | 1.15x | Overhead mínimo, bounds checks |\n| Go → WASM (TinyGo) | 1.45x | El GC de TinyGo es más simple, faltan algunas opts |\n| Python → WASM | 3.2x | componentize-py incluye intérprete CPython |\n\nEl número de Python se ve feo, pero recordar: este componente Python corre en el mismo proceso, sin overhead de container, sin cold start, y con sandboxing completo. Si la alternativa es un container Python con 800ms de cold start, 3.2x de overhead de CPU en una computación de 5ms es un trade fantástico.\n\n**Huella de memoria:**\n\n| Deployment | Memoria por instancia |\n| --- | --- |\n| 12 containers Docker (1 por componente) | ~3.2GB total |\n| 3 servicios WASM compuestos (4 componentes c/u) | ~420MB total |\n| Un solo WASM compuesto (los 12 componentes) | ~180MB |\n\nLos ahorros de memoria son dramáticos porque los componentes WASM comparten el runtime (wasmtime) y solo alocan su propia memoria lineal. La memoria lineal de un componente Rust empieza en 64KB y crece bajo demanda."},{"heading":"La Historia del Sandboxing","level":2,"url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#la-historia-del-sandboxing","content":"Esta es la parte que convence a los equipos de seguridad. Cada componente WASM:\n\n- No puede acceder al filesystem a menos que se le dé explícitamente una capability ` wasi:filesystem `\n- No puede hacer llamadas de red a menos que se le dé ` wasi:sockets `\n- No puede leer la memoria de otro componente (memorias lineales separadas)\n- No puede ejecutar syscalls arbitrarios (modelo de capabilities de WASI)\n\nConsideremos un vendor tercero que provee un componente de \"enrichment de datos\". En el mundo de containers, se necesitaría correrlo en un namespace de red separado, configurar políticas AppArmor/SELinux, armar filtrado de egress, etc. Con WASM, el componente se puede componer en el servicio con cero capabilities, puede computar sobre los datos que se le pasan y devolver resultados. Eso nomás. Si el código intenta llamar a casa, la llamada falla en el boundary de WASI, no en un firewall de red.\n\n```bash\n# Componer componente del vendor SIN capabilities WASI\nwasm-tools compose \\\n --definitions vendor-enrichment.wasm \\\n --definitions our-service.wasm \\\n -o final.wasm \\\n orchestrator.wasm\n```"},{"heading":"Errores Comunes a Evitar","level":2,"url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#errores-comunes-a-evitar","content":"**Error 1: Poner parsing de JSON en el boundary del componente.** Un primer diseño común tiene componentes intercambiando strings JSON. Esto significaba que cada llamada cross-componente pagaba por serialize + deserialize + dos alocaciones de string. Cambiar a tipos ` record ` de WIT con el ABI canónico eliminó esto completamente.\n\n**Error 2: Un componente gigante en vez de composición.** Un enfoque inicial común compila todo en un solo módulo WASM (no componente). Funcionaba pero perdía todos los beneficios de sandboxing y hacía imposible actualizar sin redeployar todo. La composición del Component Model es el punto, te deja actualizar un componente sin tocar los otros.\n\n**Error 3: Ignorar la versión del adapter.** Es posible perder tres días debuggeando un segfault que resultó ser un mismatch entre el adapter WASI preview 1 y un componente buildeado para preview 2. Fijar las versiones de tu adapter. En serio."},{"heading":"Cuándo Usar Esto (y Cuándo No)","level":2,"url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#cundo-usar-esto-y-cundo-no","content":"Utilizar el WASM Component Model cuando:\n\n- Se necesitan componer código de múltiples lenguajes en un solo proceso\n- El aislamiento de seguridad entre componentes es importante\n- Querés alta densidad de deployment (muchos servicios, memoria limitada)\n- Tus servicios son compute-heavy con intercambios de datos chicos\n\nNo lo uses cuando:\n\n- Todo tu código es un solo lenguaje (utilizar el tooling nativo de ese lenguaje)\n- Se necesitan intercambiar datasets grandes entre componentes (el overhead de copia se acumula)\n- Tu equipo no se siente cómodo con la complejidad del build toolchain\n- Se necesitan herramientas maduras de debugging/profiling (están mejorando, pero no llegaron todavía)\n\nEl Component Model no está reemplazando Kubernetes ni Docker. Es una herramienta nueva para una clase específica de problemas, y para esos problemas, es notablemente efectivo."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#questions","content":""},{"heading":"¿El WASM Component Model está listo para producción?","level":3,"url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#question-1","content":"Para case es de uso específicos, sí. Se vienen corriendo componentes WASM de Rust en Cloudflare Workers y en un orquestador custom basado en wasmtime desde Q3 2025. La spec del Component Model llegó a 1.0 a mediados de 2025, y herramientas como wasm-tools 1.2+ y cargo-component 0.16+ son estables. La brecha está en la madurez del ecosistema, la mayoría de las librerías todavía no exponen interfaces WIT."},{"heading":"¿Cómo funciona realmente la composición cross-language?","level":3,"url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#question-2","content":"Cada componente es un binario .wasm autocontenido con imports y exports tipados definidos en WIT. Un componente Go puede importar una función exportada por un componente Rust, y el runtime (wasmtime, wamr, etc.) maneja la traducción ABI. La restricción clave es que los componentes no comparten memoria, todos los datos que cruzan boundaries se copian a través del ABI canónico."},{"heading":"¿Cuál es el overhead de rendimiento comparado con microservicios nativos?","level":3,"url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#question-3","content":"Para lógica CPU-bound, los benchmarks muestran 1.2-1.8x de overhead vs Rust nativo, mayormente por bounds checking y llamadas indirectas. Para servicios I/O-bound (que es la mayoría del código real), el overhead es despreciable porque igual se está esperando red/disco. La ganancia está en la densidad de deployment, 40 componentes WASM pueden correr en la huella de memoria de 8 containers nativos."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#article-tips","content":"1. **Fijar tus versiones de WIT en un git submodule:** Las interfaces WIT son tus contratos de API. Ponelas en un repo dedicado y referenciá como submodule. Cuando corrés ` wasm-tools component new `, pasá ` --adapt wasi_snapshot_preview1=adapter.wasm ` para pinar la versión del adapter WASI también. El drift de versiones entre WIT y adapter causa mismatches de ABI silenciosos.\n2. **Utilizar ` cargo component build --release ` con LTO:** El build debug default produce archivos .wasm 5-10x más grandes. Agregar ` lto = true ` y ` opt-level = 's' ` en tu perfil release del Cargo.toml, combinado con ` wasm-opt -Oz `, típicamente lleva un componente de 2MB a 180KB.\n3. **Evitar ` String ` en los límites de interfaz WIT:** Cada string que cruza el límite del componente requiere una alocación + copia porque los componentes tienen memorias lineales separadas. Utilizar ` list<u8> ` con encoding conocido, o mejor, definí tipos ` record ` con campos de tamaño fijo. Los datos de producción muestran una reducción de latencia del 40% en una interfaz pesada de JSON al cambiar a records estructurados.\n4. **Probar el linking de componentes localmente con ` wasmtime compose `:** Antes de deployar, ejecutar ` wasm-tools compose -d deps/ -o linked.wasm your-component.wasm ` para atrapar mismatches de interfaz en build time. Los mensajes de error del linking en runtime son crípticos; la herramienta compose te da diagnósticos claros.\n\n- rust\n- webassembly\n- component-model\n- wit\n- wasm-tools\n- microservicios\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/rust-wasm-component-model-production"},{"text":"Cloudflare","url":"https://cloudflare.com/"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/rust-wasm-component-model-production#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/rust-wasm-component-model-production"},"headline":"Rust + WASM Component Model en Producción: Microservicios Políglotas Que Realmente Funcionan","description":"Construyendo microservicios políglotas en producción con Rust, el WASM Component Model y interfaces WIT. Composición cross-language, workflows con wasm-tools, y lecciones duras de correr esto a escala.","datePublished":"2025-10-08","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://lucioduran.com/favicons/og-default.png","url":"https://lucioduran.com/es/blog/rust-wasm-component-model-production","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"WebAssembly","keywords":"rust, webassembly, component-model, wit, wasm-tools, microservicios","wordCount":1484,"timeRequired":"PT8M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/rust-wasm-component-model-production"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"Rust + WASM Component Model en Producción: Microservicios Políglotas Que Realmente Funcionan","item":"https://lucioduran.com/es/blog/rust-wasm-component-model-production"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿El WASM Component Model está listo para producción?","acceptedAnswer":{"@type":"Answer","text":"Para case es de uso específicos, sí. Se vienen corriendo componentes WASM de Rust en Cloudflare Workers y en un orquestador custom basado en wasmtime desde Q3 2025. La spec del Component Model llegó a 1.0 a mediados de 2025, y herramientas como wasm-tools 1.2+ y cargo-component 0.16+ son estables. La brecha está en la madurez del ecosistema, la mayoría de las librerías todavía no exponen interfaces WIT."}},{"@type":"Question","name":"¿Cómo funciona realmente la composición cross-language?","acceptedAnswer":{"@type":"Answer","text":"Cada componente es un binario .wasm autocontenido con imports y exports tipados definidos en WIT. Un componente Go puede importar una función exportada por un componente Rust, y el runtime (wasmtime, wamr, etc.) maneja la traducción ABI. La restricción clave es que los componentes no comparten memoria, todos los datos que cruzan boundaries se copian a través del ABI canónico."}},{"@type":"Question","name":"¿Cuál es el overhead de rendimiento comparado con microservicios nativos?","acceptedAnswer":{"@type":"Answer","text":"Para lógica CPU-bound, los benchmarks muestran 1.2-1.8x de overhead vs Rust nativo, mayormente por bounds checking y llamadas indirectas. Para servicios I/O-bound (que es la mayoría del código real), el overhead es despreciable porque igual se está esperando red/disco. La ganancia está en la densidad de deployment, 40 componentes WASM pueden correr en la huella de memoria de 8 containers nativos."}}]}]}],"contentHash":"08bfc015be15cea25df794076bdf3d4ca73688a34916475b55b158c4d7b368f7","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/rust-wasm-component-model-production","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":70258,"contentBytes":14718}}
