Parte 11 — Proyecto integrador políglota · ⏱️ Duración estimada: 90 min · Nivel: Intermedio ✅ Clase construida — 10 implementaciones del núcleo verificadas contra
casos.json.
Con el inventario en la mano (clase 165), el siguiente acto de diseño es definir cómo se hablan las
piezas: sus responsabilidades y los contratos en sus fronteras. Un contrato es la promesa que un
componente le hace a los demás sobre qué datos ofrece y en qué formato. Dos componentes encajan si —y solo
si— respetan el mismo contrato en la costura que los une. Esta clase reduce esa idea a su núcleo
comprobable: dados los valores de contrato de dos lados, decidir si son compatible o incompatible.
La razón por la que esto importa es la razón por la que existen los microservicios. Newman dedica capítulos enteros a los contratos porque son lo que permite que un equipo cambie su servicio sin coordinar una reunión con los otros diez equipos. Si el backend y el frontend comparten un contrato explícito, cada uno evoluciona a su ritmo mientras la promesa se mantenga. El día que alguien cambia el contrato sin avisar, la integración se rompe en producción —no en el compilador—, que es el peor lugar donde puede romperse.
Al finalizar, podrás:
| # | Tema | Por qué importa |
|---|---|---|
| 1 | Contrato | La promesa que gobierna la frontera |
| 2 | Compatibilidad | Emisor y receptor esperan lo mismo |
| 3 | Responsabilidad | Lo que un componente hace define lo que expone |
Un contrato de frontera es el acuerdo de datos y formato entre dos componentes; su valor, dice Newman, es que hace posible el cambio independiente: mientras la promesa se cumpla, cada lado es libre por dentro. La compatibilidad es la propiedad de que emisor y receptor entienden lo mismo por lo mismo; sin ella la integración falla, y falla tarde. La responsabilidad es la tarea única de un componente y es lo que determina qué expone: un servicio que "posee" los precios es el único que puede prometer cómo se ven.
Nygard, en Release It!, mira el mismo objeto desde el otro lado y lo llama punto de integración: cada frontera es a la vez una promesa y un riesgo, el lugar por donde un sistema estable se contagia de la inestabilidad del vecino. Por eso el contrato no es burocracia: es la unidad de diseño que decide si tu sistema se puede mantener. Comprobar la compatibilidad —lo que hace el código de esta clase— es la versión mínima de lo que la industria llama contract testing (Pact): verificar la frontera sin arrancar todo el sistema.
El backend produce un formato —digamos, un JSON con un campo total— y el frontend lo consume esperando
ese mismo campo. Mientras ambos coincidan, encajan. Un día el backend renombra total a amount "porque
queda más limpio"; compila, pasa sus tests unitarios, se despliega… y el frontend deja de mostrar el
precio. Nada se cayó: simplemente el contrato se rompió en silencio. La comparación de contratos de esta
clase (compatible si los dos lados coinciden) es la forma más pequeña de atrapar ese fallo antes de
que llegue al usuario, y es el motivo por el que las pruebas de contrato viven en la frontera y no dentro
de cada servicio.
a b (los valores de contrato de cada componente)contrato=<compatible|incompatible>Especificación y verificación en casos.json:
| stdin | esperado |
|---|---|
5 5 |
contrato=compatible |
5 6 |
contrato=incompatible |
0 0 |
contrato=compatible |
LEER a, b ; compatible <- (a == b)
Mismo algoritmo, forma idiomática en cada lenguaje. Todas producen la salida de casos.json.
Cada bloque es el archivo real de implementaciones/: el enlace de cada lenguaje abre su fuente, y el comando de al lado lo ejecuta.
python/main.py · python main.pyimport sys
a, b = sys.stdin.readline().split()
print(f"contrato={'compatible' if a == b else 'incompatible'}")
🧬 El mismo programa en la familia Scripting dinámico: Ruby · Perl · Lua · Tcl · R
javascript/main.mjs · node main.mjsimport { readFileSync } from "node:fs";
const [a, b] = readFileSync(0, "utf8").trim().split(/\s+/);
console.log(`contrato=${a === b ? "compatible" : "incompatible"}`);
🧬 El mismo programa en la familia JavaScript / web: Dart · ActionScript
typescript/main.ts · pnpm exec tsx main.tsimport { readFileSync } from "node:fs";
const [a, b] = readFileSync(0, "utf8").trim().split(/\s+/);
console.log(`contrato=${a === b ? "compatible" : "incompatible"}`);
🧬 El mismo programa en la familia JavaScript / web: Dart · ActionScript
java/Main.java · java Main.javaimport java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] p = br.readLine().trim().split("\\s+");
System.out.println("contrato=" + (p[0].equals(p[1]) ? "compatible" : "incompatible"));
}
}
🧬 El mismo programa en la familia JVM: Kotlin · Scala · Groovy · Clojure
csharp/Program.cs · dotnet runusing System;
string[] p = Console.In.ReadToEnd()
.Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
Console.WriteLine($"contrato={(p[0] == p[1] ? "compatible" : "incompatible")}");
🧬 El mismo programa en la familia .NET: F# · VB.NET
go/main.go · go run main.gopackage main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
line, _ := bufio.NewReader(os.Stdin).ReadString('\n')
f := strings.Fields(line)
res := "incompatible"
if f[0] == f[1] {
res = "compatible"
}
fmt.Printf("contrato=%s\n", res)
}
🧬 El mismo programa en la familia Sistemas: Zig · Nim · D
rust/main.rs · rustc main.rs -o main && ./mainuse std::io::Read;
fn main() {
let mut s = String::new();
std::io::stdin().read_to_string(&mut s).unwrap();
let v: Vec<&str> = s.split_whitespace().collect();
let res = if v[0] == v[1] { "compatible" } else { "incompatible" };
println!("contrato={res}");
}
🧬 El mismo programa en la familia Sistemas: Zig · Nim · D
c/main.c · cc main.c -o main && ./main#include <stdio.h>
#include <string.h>
int main(void) {
char a[64], b[64];
if (scanf("%63s %63s", a, b) != 2) return 1;
printf("contrato=%s\n", strcmp(a, b) == 0 ? "compatible" : "incompatible");
return 0;
}
🧬 El mismo programa en la familia C / llaves: C++ · Objective-C
sql/main.sql · sqlite3 :memory: < main.sql-- SQL compara los valores de contrato.
WITH t(a, b) AS (VALUES (5, 5))
SELECT printf('contrato=%s', CASE WHEN a = b THEN 'compatible' ELSE 'incompatible' END) AS resultado FROM t;
🧬 El mismo programa en la familia Lógica y declarativa: Prolog · Datalog
php/main.php · php main.php<?php
[$a, $b] = preg_split('/\s+/', trim(fgets(STDIN)));
echo "contrato=" . ($a === $b ? "compatible" : "incompatible") . "\n";
🧬 El mismo programa en la familia Scripting dinámico: Ruby · Perl · Lua · Tcl · R
SQL es declarativo: no lee de stdin como los demás; su implementación muestra la misma idea sobre una tabla de casos, y el verificador la marca como ilustrativa.
El contrato (casos.json) es una comparación de igualdad: la entrada 5 5 es compatible,
5 6 es incompatible. Trivial en apariencia, pero el "cómo se compara" esconde una de las diferencias
semánticas más traicioneras entre lenguajes.
En Python la lógica es una línea: a, b = sys.stdin.readline().split() desempaqueta los dos tokens y
'compatible' if a == b else 'incompatible' decide. Aquí a y b son cadenas, y en Python == compara
valor: "5" == "5" es verdadero. La f-string imprime el resultado. Nada sorprende.
Java cuenta la misma historia pero con una trampa clásica que esta clase esquiva a propósito:
String[] p = br.readLine().trim().split("\\s+");
System.out.println("contrato=" + (p[0].equals(p[1]) ? "compatible" : "incompatible"));
Fíjate en p[0].equals(p[1]) y no p[0] == p[1]. En Java == sobre objetos compara referencias
(si son el mismo objeto en memoria), no contenido; para comparar el texto hay que usar equals. Un
principiante que "traduce" el a == b de Python a Java carácter por carácter obtiene un programa que a
veces acierta (por el string interning) y a veces falla —el peor tipo de bug—. Bloch, en Effective
Java, dedica un ítem entero a esto: no es una diferencia sintáctica, es semántica.
C no tiene ni siquiera un operador de igualdad para texto: usa strcmp(a, b) == 0, porque una cadena
es un puntero y a == b compararía direcciones. Go y Rust, en cambio, sí definen == sobre sus
cadenas como comparación de valor (f[0] == f[1], v[0] == v[1]), acercándose a la intuición de Python.
SQL lo dice de la forma más limpia: CASE WHEN a = b THEN 'compatible' ELSE 'incompatible' END, una
expresión declarativa sobre una tabla. Diez formas de preguntar "¿son iguales?", y tres respuestas
distintas a la pregunta más profunda: ¿iguales en qué sentido?
El contrato de esta clase es una igualdad, y la igualdad es donde los lenguajes esconden sus decisiones más finas. La tabla lo condensa; el texto lo justifica.
| Clase de diferencia | Observación entre lenguajes |
|---|---|
| Sintáctica | == (Python/Go/Rust/JS), .equals (Java), strcmp (C), = (SQL): el mismo predicado, distinta escritura. |
| Semántica | En Java y C, == sobre cadenas compara identidad/puntero, no contenido: hay que usar equals/strcmp. En Python, Go, Rust y PHP (===) compara valor. |
| Paradigmática | Los imperativos ejecutan una comparación; SQL declara la compatibilidad como una columna calculada con CASE. |
La moraleja conecta con la tesis del curso: portar un contrato de un lenguaje a otro cambiando solo la sintaxis es exactamente el error que Newman advierte a escala de sistema —cambiar la frontera sin entender su semántica—. La igualdad de cadenas es ese mismo peligro a escala de una línea.
Los tests de contrato (Pact, Spring Cloud Contract) verifican que servicios independientes respetan su frontera sin necesidad de levantarlos todos juntos. Son la generalización de lo que hace este programa: comparar lo que un lado promete con lo que el otro espera. En lenguajes con tipos, el propio compilador es un verificador de contratos internos; entre servicios, el contrato se hace explícito y se prueba aparte.
Los mismos casos para todas las implementaciones: casos.json. Verifica la equivalencia:
python scripts/verificar_equivalencia.py 166
Detalle en reto.md.
== sobre objetos/cadenas en Java o C compara identidad → solución: usa equals/strcmp para comparar contenido.Libros de la parte:
Libros de los lenguajes del núcleo:
⏮️ Clase 165 · 📂 Parte · 📚 Índice · 🌐 Atlas · Clase 167 ⏭️