CodeGym /Cursos /Java Server /API HTTP externa en ReadLater Starter

API HTTP externa en ReadLater Starter

Java Server
Nivel 14, Lección 0
Disponible

1. Backend como cliente HTTP

Si hasta ahora el backend en tu cabeza se veía como «un servidor que solo recibe solicitudes», hoy vamos a mover un poco esa imagen. En realidad, un servicio backend casi siempre vive rodeado de otros servicios y API: le pregunta a alguien, envía datos a alguien, consulta detalles con alguien. Y eso es normal.

Imagina un día laboral típico de cualquier servicio «normal»: recibe una solicitud del usuario, luego va al proveedor de pagos externo, luego consulta los datos de envío en el servicio de logística, luego envía un correo a través de la API de correo — y solo entonces devuelve la respuesta al usuario. A veces da la impresión de que el backend en realidad no «procesa la lógica de negocio», sino que dirige cuidadosamente las API de otros (bueno, un director de orquesta también es una profesión, por cierto).

Para establecer términos claros de inmediato, es útil distinguir dos direcciones:

Dirección Qué sucede Quién es «cliente», quién es «servidor»
Inbound HTTP (entrante) Alguien llama a nosotros cliente = usuario/navegador/aplicación móvil, servidor = nuestro backend
Outbound HTTP (saliente) Nosotros llamamos a alguien cliente = nuestro backend, servidor = API externa/servicio vecino

Hoy nos ocupamos precisamente de la parte outbound: nuestra aplicación Java enviará una solicitud HTTP a una API externa y recibirá la respuesta. Es decir, se comportará... como un Postman muy serio, pero con carácter Java y hábito de tipos estrictos (aún sin tipificación — pero Java ya se está preparando mentalmente).

2. Catálogo de libros para ReadLater Starter

El dominio de ReadLater Starter es simple y humano: una lista personal de «leer después». Pero en cuanto intentes hacer esta lista un poco más conveniente, surge la pregunta: ¿de dónde obtener los datos del libro? Y aquí el catálogo externo resulta no ser un «complemento», sino una fuente de información completamente lógica.

Podrías, por supuesto, obligar al usuario a introducir todo manualmente: título, autor, año, editorial y además «quién tradujo». Pero entonces nuestro servicio se convertirá rápidamente en una aplicación «Adivina cómo se escribe el apellido del autor», y el usuario empezará a copiar datos de internet manualmente. Es incómodo y propenso a errores.

El catálogo externo de libros en nuestro proyecto desempeña el papel de fuente de datos de referencia. En la práctica se ve así: el usuario introduce una cadena de búsqueda (por ejemplo, clean code), enviamos una solicitud al catálogo, obtenemos una lista de resultados, el usuario selecciona un libro por su identificador externo, y podemos obtener una ficha más detallada. Incluso si luego tenemos una lista de lectura local, el catálogo sigue siendo útil como «directorio» que sabe encontrar libros más rápido y con mayor precisión que una persona cansada después del trabajo.

Y un punto metodológico crítico: ya hemos investigado el contrato de este catálogo mediante Postman. Es decir, hoy no estamos «intentando adivinar la URL correcta». Hacemos algo ingenierilmente honesto: trasladamos un contrato ya verificado al código. Es como «primero medimos con cinta métrica, luego cortamos», y no al revés (aunque, sí, en programación a veces se corta al revés — pero intentaremos hacerlo mejor).

3. Llamada local vs HTTP externo

Dentro de un programa Java estamos acostumbrados a un modelo muy acogedor: llamamos a un método y obtenemos un resultado. Todo en un mismo proceso, un heap, un depurador, podemos poner un breakpoint y sentirnos dueños del mundo. Con una llamada HTTP, el cerebro intenta aplicar el mismo modelo — y ahí es donde suelen empezar los «¿por qué no funciona?».

Para comparar, aquí hay una llamada local «normal» — dentro de un mismo proceso:

// Llamada local: todo ocurre en nuestro proceso, sin red ni estados HTTP
String result = localCatalog.findById("OL12345M");

// Aquí imprimimos el resultado ya listo (por ejemplo, cadena con el título del libro)
System.out.println(result); // (por ejemplo) Clean Code by Robert C. Martin

En este código no hay dirección, ni red, ni estado, ni encabezados. Si el método falla, (probablemente) entenderás rápidamente dónde: depurador, stack trace, todo cerca.

Y ahora — un anticipo de una llamada externa. Ni siquiera la ejecutamos, solo mira la forma:

import java.net.URI;
import java.net.http.HttpRequest;

// Dirección de la API externa: ya no es una "llamada de método", sino una URL/URI concreta
URI uri = URI.create("https://catalog.example/books/OL12345M");

HttpRequest request = HttpRequest.newBuilder(uri)
        // Encabezado: decimos explícitamente que queremos JSON
        .header("Accept", "application/json")
        // Método HTTP: leemos datos (en lugar de modificarlos)
        .GET()
        .build();

Aquí se ve claramente «otro universo». Aparece una dirección URI, aparece un método HTTP, aparece un encabezado Accept. Y lo principal: incluso si la solicitud parece correcta, eso no garantiza éxito. El servidor puede devolver 404, puede devolver 500, puede devolver un formato inesperado, o puede... no responder. Y eso no es «exótico». Es la vida normal de la red.

Si quieres fijar las diferencias en un solo lugar, aquí tienes una tabla muy práctica:

Característica Llamada local a método Llamada HTTP a API externa
Dónde se ejecuta el código En tu proceso En un servidor ajeno
Datos de entrada Parámetros del método URI + método + encabezados (+ a veces body)
Resultado Valor de retorno Estado + encabezados + body
Error Excepción en tu código Puede ser excepción (red) o respuesta HTTP correcta con estado de error
«¿Se puede depurar dentro?» Generalmente sí No (no es tu código)

Y de aquí nace el hábito clave del desarrollador backend: una llamada HTTP siempre debe pensarse como una interacción contractual, no como un «método que se ejecuta en algún lugar».

4. Contrato HTTP de una llamada externa

Cuando empiezas a trabajar con una API externa, dan ganas de pensar: «bueno, ahí hay JSON, de alguna manera lo leeremos». Pero es mejor pensar de otra manera: «estoy firmando un contrato con un servicio ajeno». En ese contrato hay cláusulas claras: a dónde y cómo llamo, y qué prometen devolverme en respuesta. De lo contrario, terminarás rápidamente en la situación de «ayer funcionaba, hoy no».

En un contrato HTTP hay varias partes obligatorias. Ya las hemos visto antes, pero hoy son importantes desde la perspectiva de la llamada saliente (es decir, cuando el cliente somos nosotros):

Parte del contrato Dónde se manifiesta en el código Pregunta que debes hacerte
URI URI.create(...) ¿A dónde voy? ¿Es la dirección exacta?
Method .GET(), .POST(), … ¿Qué hago: leo o modifico?
Headers .header("Accept", "...") ¿En qué formato quiero la respuesta?
Status code response.statusCode() ¿Es éxito o error? ¿Qué clase de problema?
Body response.body() ¿Qué exactamente se devolvió? ¿Se parece a lo que esperábamos?

Y esta es una idea importante: el contrato no es solo el body. Un novato a menudo mira solo el JSON y olvida que 200 y 404 son historias completamente diferentes. Incluso si en ambos casos el cuerpo de la respuesta se parece a JSON (y a veces es así), el significado es totalmente diferente.

Si lo ponemos de forma muy terrenal: una API externa es como un «ticket de compra». No solo es importante lo que hay dentro del paquete (body), sino también lo que está escrito arriba: si la compra fue exitosa (status), qué condiciones (headers), y si la dirección de la tienda es correcta (URI). De lo contrario, corres el riesgo de llevarte a casa no productos, sino una sensación de vergüenza y un montón de errores.

5. Postman: borrador de código

La buena noticia: hoy no necesitamos jugar a «adivina el endpoint correcto». Ya hemos ido al catálogo mediante Postman, ya hemos visto ejemplos de solicitudes y respuestas, ya hemos probado variantes. Es decir, estamos haciendo lo que en el desarrollo real se llama «primero investigar el contrato, luego implementar».

Es útil imaginar mentalmente que la solicitud de Postman es un borrador del código futuro. Más o menos así (formato condicional, pero el significado es reconocible):

# Consulta de búsqueda al catálogo (borrador desde Postman)
GET {{baseUrl}}/search?q=clean%20code
# Le decimos al servidor que esperamos JSON en la respuesta
Accept: application/json

Esto no son solo «líneas bonitas». Es un acuerdo registrado: método GET, ruta concreta, parámetro de consulta q, y expectativa de respuesta JSON.

Y he aquí por qué este paso es metodológicamente importante. Cuando escribes tu primer cliente HTTP en Java, es muy fácil caer en la magia: «bueno, de alguna manera envía la solicitud». Postman te devuelve a la tierra: ahí se ve todo. El método se ve. La URL se ve. Los encabezados se ven. La respuesta se ve. Y la tarea de hoy es trasladar esa transparencia al código, no esconderla bajo la alfombra de un «cliente universal para el futuro».

6. Éxito mínimo: estado y raw body

En el primer paso, es muy útil reducir las expectativas al mínimo adecuado. Hoy no estamos construyendo una arquitectura ideal, no «diseñamos un SDK de cliente», no convertimos JSON en bonitos objetos Java. Nuestro criterio de éxito es más simple y honesto: la aplicación envió una solicitud, recibió una respuesta y pudo mostrarnos su significado.

Aquí aparece un término que usaremos durante todo el nivel: raw body. Es el cuerpo de la respuesta leído «tal cual», sin mapeo a DTO. Es decir, básicamente una cadena JSON. Y sí, para un desarrollador Java suena como «puaj, una cadena», pero en un paso de aprendizaje es exactamente lo que se necesita: primero asegurarse de que el transporte funciona y el contrato coincide, y luego pensar en tipos.

Esquemáticamente, nuestro flujo mínimo de hoy es así:

sequenceDiagram
    participant U as "Usuario"
    participant A as "ReadLater Starter (Java)"
    participant C as "Catálogo externo de libros"

    Note over U,A: "El usuario ejecuta el comando y espera un resultado observable"
    U->>A: "Ejecución del comando 'catalog search ...'"
    Note over A,C: "Es importante ver el contrato: URI, método, encabezados, estado y body"
    A->>C: "HTTP GET (URI + Accept)"
    C-->>A: "HTTP response (status + headers + JSON body)"
    A-->>U: "Impresión del estado y JSON crudo (preview)"

Observa que la «respuesta» aquí no es solo JSON. Incluso a nivel de «simplemente imprimir en consola», queremos ver al menos tres cosas: estado, encabezados (al menos una vez) y cuerpo. Porque así es como un desarrollador backend aprende a no perder partes importantes del contrato.

7. Paso repetible

Es muy importante que la primera llamada externa no se convierta en «código en un método separado que nadie llama nunca». Al final del día, debes poder ejecutar la aplicación y obtener un resultado observable: la solicitud externa realmente se fue, la respuesta realmente llegó, y realmente ves JSON. Esto convierte el tema de «teoría sobre HTTP» en un hábito.

Y al mismo tiempo, deliberadamente no complicamos las cosas. Por ahora será suficiente incluso con este «esqueleto de interacción» (aún sin detalles de implementación, solo como idea):

// Realizamos la consulta al catálogo y obtenemos JSON 'crudo' como cadena
String json = catalogClient.searchRaw("clean code");

// Para el primer paso es conveniente imprimir una vista previa para no abrumar la consola con megabytes de JSON
int previewLength = Math.min(json.length(), 200);
System.out.println(json.substring(0, previewLength) + "...");
// { "docs": [ ... ] }...

Este fragmento es importante no porque sea «bonito», sino porque hace que la integración externa sea parte de la aplicación. Es decir, podemos repetir la ejecución, comparar respuestas, entender qué cambia y gradualmente poner el código en orden. Es exactamente el mismo principio por el que antes fijamos el Gradle Wrapper y los comandos de ejecución: si un paso se puede repetir, entonces realmente existe, y no «funcionó por casualidad una vez en mi ordenador».

Y sí, en este momento puede aparecer el deseo de envolverlo todo inmediatamente en diez capas de abstracciones. Es un deseo normal (especialmente si has visto bonitas imágenes arquitectónicas). Pero hoy nuestra tarea es al revés: dejar la mecánica visible, para que el cerebro se acostumbre a la realidad de una llamada HTTP. La belleza y las «capas adultas» las añadiremos más tarde — cuando haya algo sobre lo que añadirlas.

8. Errores típicos en la primera llamada outbound HTTP

Cuando empiezas a hacer llamadas HTTP salientes, inevitablemente las comparas con las llamadas a métodos habituales y esperas el mismo comportamiento. De ahí surgen errores que en la práctica ocurren incluso entre personas inteligentes — simplemente porque «el cerebro aún no ha aceptado las reglas del juego». Mejor detectarlos al principio.

Error nº 1: pensar que el backend «solo recibe solicitudes», y que «salir al exterior» es algo raro.
Debido a esta mentalidad, la llamada externa se percibe como una característica extraña puntual, y no como una parte normal del sistema. Como resultado, el código se pega «donde sea», sin disciplina. Es útil aceptar de inmediato: outbound HTTP es una rutina tan normal como trabajar con cadenas (lamentablemente).

Error nº 2: mentalmente equiparar una llamada HTTP a someService.method() e ignorar el contrato.
En cuanto olvidas que HTTP tiene dirección, método, encabezados y códigos de estado, empiezas a mirar solo el body. Esto es peligroso: puedes recibir un estado de error y aun así intentar leer el body como «respuesta exitosa». El hábito correcto: primero el estado, luego todo lo demás.

Error nº 3: esconder la primera llamada detrás de un «cliente universal para el futuro».
Es el caso en que la «arquitectura» mata el aprendizaje. Si en el primer paso creas cinco clases y tres interfaces, perderás lo más valioso: entender cómo se ve realmente una solicitud y una respuesta. En la etapa inicial, es mejor una llamada pequeña y comprensible que un «motor perpetuo» de abstracciones.

Error nº 4: escribir código sin apoyarse en el contrato ya investigado de Postman.
A veces quieres: «bueno, ahora escribo rápido una URL como cadena». Al final, pierdes tiempo adivinando, no aprendiendo. Si el contrato ya está investigado, úsalo como mapa. La colección de Postman no es un archivo «para recordar», sino documentación de trabajo para el código.

Error nº 5: considerar el raw JSON en consola como «el final de la tarea».
Raw JSON es un excelente resultado intermedio, pero solo intermedio. Si te detienes aquí, la aplicación se queda en el nivel de «imprimimos texto incomprensible». Hoy raw es una herramienta para verificar el contrato y el transporte. Más adelante (hoy no) haremos que la respuesta sea utilizable para el código real.

1
Tarea
Java Server, nivel 14, lección 0
Bloqueada
Llamada local y contrato externo
Llamada local y contrato externo
1
Tarea
Java Server, nivel 14, lección 0
Bloqueada
Comando `catalog search` como descripción de la solicitud saliente
Comando `catalog search` como descripción de la solicitud saliente
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION