REST en la práctica: recursos, representaciones y relaciones
En diseño REST, el foco no está en “llamar funciones” sino en exponer un modelo de recursos del dominio. Un recurso es cualquier “cosa” del dominio que tenga identidad estable y pueda referenciarse (normalmente con una URL). El cliente no manipula el recurso “en sí”, sino una representación del recurso (por ejemplo JSON) que describe su estado y enlaces relevantes. Además, los recursos no viven aislados: existen relaciones (pertenencias, asociaciones, dependencias) que conviene expresar con URLs y vínculos.
Recurso vs representación
- Recurso: concepto del dominio con identidad (p. ej., un pedido #123, un cliente #88, una factura #F-2026-01).
- Representación: el “formato” que viaja por la red (p. ej., JSON con campos del pedido). Puede variar sin que cambie el recurso.
- Relaciones: conexiones navegables entre recursos (p. ej., un pedido pertenece a un cliente; un pedido tiene líneas; una factura se emite a partir de un pedido).
Una forma práctica de comprobar si algo es un recurso: ¿puedo enlazarlo, listarlo, consultarlo y su identidad tiene sentido para el negocio?
Relaciones como URLs y vínculos
Las relaciones pueden expresarse de dos maneras complementarias:
- Subrecursos (estructura en la ruta): cuando la relación es de contención/jerarquía fuerte. Ej.:
/customers/{customerId}/orders. - Vínculos en la representación: cuando quieres guiar navegación o expresar asociaciones sin acoplar todo a la ruta. Ej.: incluir
linkscon URLs a recursos relacionados.
{ "id": "ord_123", "status": "paid", "total": 125.50, "customerId": "cus_88", "links": { "self": "/orders/ord_123", "customer": "/customers/cus_88", "items": "/orders/ord_123/items", "invoice": "/invoices/inv_9001" } }Cómo identificar recursos a partir del dominio
El punto de partida no es la lista de endpoints, sino los requisitos funcionales y el lenguaje del negocio. A partir de ahí, se extraen candidatos a recursos y se decide su forma (colección, elemento, subrecurso, relación).
Paso 1: extraer sustantivos y “cosas con identidad”
De historias de usuario o requisitos, subraya sustantivos y objetos que el negocio reconoce. Ejemplo de requisitos (dominio: e-commerce):
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
- “Un cliente puede ver sus pedidos y el detalle de cada pedido.”
- “Un cliente puede añadir productos al carrito y confirmar la compra.”
- “El sistema genera un reporte diario de ventas por categoría.”
Candidatos iniciales: customers, orders, order-items, carts, products, reports, categories.
Paso 2: clasificar candidatos (entidad, subrecurso, relación)
- Entidades principales: tienen ciclo de vida propio y suelen existir “por sí mismas” (clientes, productos, pedidos).
- Subrecursos: existen dentro del contexto de un padre (líneas de pedido dentro de un pedido; items del carrito dentro del carrito).
- Relaciones: vínculos entre entidades (pedido → cliente; pedido → factura; producto → categoría). A veces se modelan como recursos propios si tienen atributos (p. ej., una “membresía” con fecha de alta, rol, estado).
Paso 3: decidir colecciones y elementos
En REST, es común distinguir:
- Colección: conjunto de recursos del mismo tipo. Ej.:
/orders,/customers. - Elemento: un recurso concreto dentro de una colección. Ej.:
/orders/{orderId}.
Regla práctica: si puedes “listar” y “crear” en un conjunto, probablemente necesitas una colección; si puedes “consultar/actualizar” un objeto concreto, necesitas el elemento.
| Tipo | Ejemplo | Cuándo usar |
|---|---|---|
| Colección | /orders | Listar pedidos, crear un pedido |
| Elemento | /orders/{orderId} | Consultar/actualizar un pedido específico |
| Subcolección | /customers/{id}/orders | Listar pedidos de un cliente (contexto fuerte) |
| Subelemento | /orders/{id}/items/{itemId} | Operar sobre una línea concreta |
Paso 4: definir atributos (estado) y límites
Para cada recurso, define atributos que representen su estado y evita mezclar responsabilidades. Preguntas útiles:
- ¿Qué campos son esenciales para identificar y mostrar el recurso?
- ¿Qué campos cambian con el tiempo (estado)?
- ¿Qué campos son derivados (se pueden calcular) y conviene exponer o no?
- ¿Qué datos pertenecen a otro recurso (y deberían ser un vínculo en lugar de duplicación)?
Ejemplo (pedido):
- Propios:
id,status,createdAt,total,currency - Referencias:
customerIdy enlace a/customers/{id} - Subrecursos:
/orders/{id}/items,/orders/{id}/payments
Paso 5: modelar relaciones navegables (vínculos)
Además de la ruta, incluye vínculos que ayuden a descubrir acciones válidas sin convertir la API en un “mapa de funciones”. Un patrón simple es un objeto links con URLs relevantes (self, related). En dominios complejos, también puedes incluir metadatos del vínculo (tipo, método sugerido), pero mantén consistencia.
Criterios para decidir: recurso vs acción
Una fuente común de diseños tipo RPC es convertir cada verbo del negocio en un endpoint (/doSomething, /calculate, /confirmOrder). En REST, primero intentas expresar el cambio como una transición de estado de un recurso o la creación de un recurso nuevo.
Checklist práctico
- ¿Hay un “resultado” con identidad? Si el resultado se puede consultar después, probablemente es un recurso. Ej.: “generar factura” →
/invoices(se crea una factura) en vez de/generateInvoice. - ¿Es un cambio de estado de un recurso existente? Modela el estado como atributo y actualízalo. Ej.: confirmar pedido → actualizar
statusdel pedido o crear un recurso “confirmación” si necesitas auditoría. - ¿Es una operación efímera sin identidad? Aun así, a menudo puedes modelarla como recurso “proceso” o “job” si es asíncrona. Ej.: exportación →
/exports(creas un export y luego consultas su estado/descarga). - ¿El verbo es del dominio o es técnico? Verbos técnicos (“validate”, “process”) suelen esconder recursos. Pregunta: ¿qué entidad del negocio se valida/procesa?
Ejemplos de transformación de “acciones” a recursos
| Requisito | Diseño tipo RPC (evitar) | Diseño orientado a recursos |
|---|---|---|
| Confirmar un pedido | POST /orders/{id}/confirm | PATCH /orders/{id} con {"status":"confirmed"} o POST /order-confirmations con referencia al pedido |
| Reembolsar un pago | POST /payments/{id}/refund | POST /refunds con {"paymentId":"...","amount":...} |
| Exportar ventas | POST /exportSales | POST /exports (crea job) y GET /exports/{id} (estado/URL de descarga) |
| Calcular impuestos | POST /calculateTax | Si es parte del pedido: exponer tax en /orders/{id}; si es simulación: POST /tax-quotes |
Nota: a veces un endpoint “verbal” es aceptable si representa un subrecurso claramente acotado y estable, pero el objetivo es que el modelo no se convierta en un catálogo de métodos.
Cuándo usar endpoints específicos como /reports sin caer en RPC
Hay casos donde el dominio incluye “artefactos” que no son entidades transaccionales típicas, como reportes, dashboards, exportaciones o agregados. Se pueden diseñar de forma REST si los tratas como recursos con identidad, estado y/o parámetros bien definidos.
Patrones recomendados
- Reportes como recursos consultables:
GET /reports/sales-daily?date=2026-02-01. Aquísales-dailyes un tipo de reporte (recurso lógico) y la consulta define la instancia de la representación. - Reportes materializados (con identidad):
POST /reportspara crear un reporte (job), luegoGET /reports/{reportId}para ver estado y resultado. Útil si tarda o si quieres auditoría. - Agregados del dominio: si el negocio habla de “resumen de cuenta”, puede ser un recurso:
GET /customers/{id}/account-summary.
Señales de que estás cayendo en RPC
- Rutas con verbos genéricos:
/runReport,/getSales,/processEndOfDay. - Un solo endpoint que recibe un parámetro
actiony hace cosas distintas. - La mayoría de endpoints son “comandos” sin recursos navegables ni resultados persistentes.
Guía paso a paso: de requisitos a inventario de recursos
Usa este procedimiento para convertir requisitos en un inventario inicial de recursos, con atributos y vínculos. El objetivo no es “cerrar” el diseño, sino producir un mapa consistente para iterar.
Paso 1: listar casos de uso en lenguaje del negocio
Escribe 6–10 frases del tipo “Como X, quiero Y para Z”. Ejemplo (plataforma de suscripciones):
- Como usuario, quiero ver los planes disponibles.
- Como usuario, quiero suscribirme a un plan.
- Como usuario, quiero ver el estado de mi suscripción y mi próxima fecha de cobro.
- Como usuario, quiero cancelar mi suscripción.
- Como admin, quiero ver un reporte mensual de ingresos.
Paso 2: extraer candidatos a recursos (sustantivos) y eventos
- Recursos:
plans,subscriptions,users,invoices,reports. - Eventos/acciones: “suscribirse”, “cancelar”. Intenta mapearlos a cambios de estado o creación de recursos.
Paso 3: definir colecciones, elementos y subrecursos
/plansy/plans/{planId}/subscriptionsy/subscriptions/{subscriptionId}- Si la suscripción pertenece fuertemente al usuario:
/users/{userId}/subscriptions(y opcionalmente también/subscriptions/{id}si se accede globalmente) - Facturas de una suscripción:
/subscriptions/{id}/invoices
Paso 4: proponer atributos mínimos por recurso
| Recurso | Atributos sugeridos | Notas |
|---|---|---|
Plan | id, name, price, currency, billingPeriod, features | Generalmente de solo lectura para usuarios |
Subscription | id, userId, planId, status, startedAt, nextBillingAt, cancelAtPeriodEnd | status modela “cancelada/activa/past_due” |
Invoice | id, subscriptionId, amount, currency, status, issuedAt, dueAt | Puede incluir enlace a descarga |
Report | id (si materializado), type, period, status, createdAt, resultUrl | Si es síncrono, puede ser solo GET con query |
Paso 5: definir vínculos clave (navegación)
Para cada recurso, define 3–6 vínculos que un cliente necesitará con frecuencia.
{ "id": "sub_10", "status": "active", "planId": "plan_basic", "userId": "usr_7", "nextBillingAt": "2026-03-01", "links": { "self": "/subscriptions/sub_10", "plan": "/plans/plan_basic", "user": "/users/usr_7", "invoices": "/subscriptions/sub_10/invoices" } }Paso 6: resolver “acciones” con decisiones explícitas
Para cada verbo del negocio, decide una de estas opciones y documenta el porqué:
- Cambio de estado en un recurso existente (p. ej., cancelar →
statusocancelAtPeriodEnd). - Creación de un recurso que representa el resultado (p. ej., suscribirse → crear
subscription). - Creación de un recurso de proceso si es asíncrono (p. ej., generar reporte → crear
reportconstatus).
Ejemplo de mapeo:
- “Suscribirse a un plan” →
POST /subscriptionscon{"userId":"usr_7","planId":"plan_basic"} - “Cancelar suscripción” →
PATCH /subscriptions/{id}con{"cancelAtPeriodEnd":true}(o{"status":"canceled"}si es inmediata) - “Reporte mensual de ingresos” →
GET /reports/revenue-monthly?month=2026-01oPOST /reportscon{"type":"revenue-monthly","month":"2026-01"}
Ejercicio guiado: convertir requisitos en inventario de recursos
Objetivo: producir un inventario de recursos con (1) nombre, (2) colección/elemento, (3) atributos, (4) relaciones/vínculos, (5) notas de “acción vs recurso”.
Enunciado
Supón que estás diseñando una API para una biblioteca digital:
- Los usuarios pueden buscar libros por título/autor.
- Los usuarios pueden ver el detalle de un libro y su disponibilidad.
- Los usuarios pueden reservar un libro si no está disponible.
- Los usuarios pueden ver sus reservas activas y cancelarlas.
- Los bibliotecarios generan un reporte semanal de préstamos.
Parte A: extracción de recursos (10 minutos)
- Lista sustantivos: libros, autores, usuarios, reservas, préstamos, reportes.
- Marca cuáles tienen identidad clara: libro, usuario, reserva, préstamo, reporte (posible), autor (posible).
Parte B: inventario inicial (15 minutos)
Completa una tabla como esta (rellena con tus decisiones):
| Recurso | Colección | Elemento | Atributos mínimos | Vínculos/relaciones | Notas |
|---|---|---|---|---|---|
| Book | /books | /books/{bookId} | id, title, authors, availability | self, authors, reservations | Búsqueda con query en colección |
| Reservation | /reservations o /users/{id}/reservations | /reservations/{reservationId} | id, userId, bookId, status, createdAt | book, user | “Reservar” = crear reserva |
| Report | /reports o /reports/loans-weekly | /reports/{reportId} (si materializado) | type, period, status, result | self | Decidir síncrono vs job |
Parte C: decisiones “recurso vs acción” (10 minutos)
- “Buscar libros” → no es un recurso nuevo: es una consulta sobre
/books(p. ej.,GET /books?query=...). - “Reservar un libro” → crear
reservation(p. ej.,POST /reservationsconbookIdyuserId). - “Cancelar reserva” → cambio de estado de
reservation(p. ej.,PATCH /reservations/{id}con{"status":"canceled"}). - “Generar reporte semanal” → si tarda, crear recurso
report(job) y consultar su estado.
Parte D: revisión de consistencia (5 minutos)
- ¿Las rutas son sustantivos en plural para colecciones?
- ¿Las relaciones importantes tienen vínculo o subrecurso?
- ¿Las “acciones” quedaron expresadas como creación/cambio de estado?
- ¿Hay endpoints que parecen funciones? Si sí, intenta re-modelarlos como recursos.