Introducción: REST o GraphQL — el debate eterno que resolveremos en Rust
Si programas en Rust y estás eligiendo una arquitectura para tu API, seguramente te has preguntado: ¿qué elegir — el clásico REST o el moderno GraphQL? Ambos enfoques tienen un ejército de fanáticos y una montaña de detractores. Acusan a REST de redundancia de datos y de "charla" excesiva entre cliente y servidor. A GraphQL, de la complejidad del almacenamiento en caché y de consultas "infernales" a la base de datos.
Pero seamos realistas: Rust no es JavaScript ni Python. Su filosofía son las zero-cost abstractions y el máximo rendimiento. ¿Significa esto que uno de los enfoques se adapta mejor al ecosistema de Rust? Averigüémoslo con ejemplos reales de código.
En este artículo no solo enumeraremos pros y contras. Escribiremos dos microservicios con actix-web (REST) y async-graphql, compararemos su rendimiento, complejidad de desarrollo y facilidad de mantenimiento. ¡Allá vamos!
Breve introducción: REST y GraphQL en 30 segundos
REST (Representational State Transfer) es un estilo arquitectónico donde cada recurso (usuario, artículo, pedido) tiene su propia URL. Obtienes datos mediante GET, los creas mediante POST, los actualizas mediante PUT/PATCH. Todo es simple y predecible. Pero a menudo obtienes más datos de los necesarios, o haces varias solicitudes para reunir entidades relacionadas.
GraphQL es un lenguaje de consultas y un entorno de ejecución. El cliente decide qué campos necesita. Una sola consulta y obtienes exactamente lo que pediste. Pero esta flexibilidad se paga con la complejidad de implementación en el servidor y posibles problemas de rendimiento.
En Rust, ambos enfoques se implementan al más alto nivel gracias al sistema de tipos y la asincronía. Pero profundicemos.
REST en Rust: lo clásico con actix-web
Empecemos con el conocido REST. Como ejemplo, tomemos un blog simple: hay usuarios, artículos y comentarios. En REST, necesitaremos crear varios endpoints.
use actix_web::{web, App, HttpServer, Responder, HttpResponse};use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]struct Article { id: i32, title: String, content: String, author_id: i32,}
async fn get_article(path: web::Path<i32>) -> impl Responder { let article_id = path.into_inner(); // Imaginemos que obtenemos el artículo de la BD let article = Article { id: article_id, title: "GraphQL vs REST".to_string(), content: "Comparación detallada...".to_string(), author_id: 1, }; HttpResponse::Ok().json(article)}
async fn get_articles_by_author(path: web::Path<i32>) -> impl Responder { let author_id = path.into_inner(); // Lista de artículos del autor let articles = vec![ Article { id: 1, title: "Artículo 1".to_string(), content: "Texto...".to_string(), author_id }, Article { id: 2, title: "Artículo 2".to_string(), content: "Texto...".to_string(), author_id }, ]; HttpResponse::Ok().json(articles)}
#[actix_web::main]async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .route("/articles/{id}", web::get().to(get_article)) .route("/authors/{id}/articles", web::get().to(get_articles_by_author)) }) .bind("127.0.0.1:8080")? .run() .await}Todo se ve limpio y claro. Ventajas de REST en Rust:
- Simplicidad del almacenamiento en caché a nivel HTTP (ETag, Cache-Control).
- Estructura transparente: cada endpoint hace una cosa.
- Fácil integración con cualquier cliente (curl, Postman, navegador).
Pero también tiene desventajas:
- Problema de consultas N+1. Para obtener un artículo y su autor, se necesitan dos consultas. O crear un endpoint separado con JOIN.
- Over-fetching.<