Saltar al contenido
🐍 Curso de Python
/

Módulo 5 · Lección 11 de 30

Diseñar los recursos y las rutas

Las decisiones que hay que tomar antes de escribir una línea, y por qué la API no debe parecerse a tus tablas.

⏱ 14 min de lectura

Cambiamos de lado. Las diez lecciones anteriores consumían APIs ajenas; a partir de aquí escribimos la nuestra, con Django REST Framework.

Y antes de instalar nada hay que decidir cómo va a ser, porque una API publicada no se puede cambiar. Los modelos se refactorizan cuando quieras; las direcciones y los nombres de los campos, no: hay programas ajenos que dependen de ellos y que no controlas.

El dominio#

Vamos a hacer el catálogo de una editorial, que tiene lo justo para que aparezcan todos los casos difíciles:

Salida en pantalla
Editorial   nombre, país
Autor       nombre, nacimiento
Libro       título, ISBN, precio, páginas, estado, editorial, autores
Reseña      libro, autor, puntuación, texto

Ahí hay una clave ajena, una relación de muchos a muchos, un campo con estados, un decimal que es dinero y un recurso que solo tiene sentido dentro de otro. Suficiente.

La API no es la base de datos#

Es la confusión que anunció la lección 1 y la que más caro se paga. Publicar los modelos tal cual es cómodo el primer día y es una atadura para siempre.

Tres razones concretas:

Te ata al esquema. Si Libro tiene una columna paginas y la API expone paginas, el día que decidas moverla a otra tabla tienes que mantener el nombre igual. La API es un contrato; el esquema es un detalle de implementación que debería poder cambiar.

Expone lo que no debe. El fields = '__all__' que veremos en la lección siguiente publica todos los campos, incluidos los que añadas mañana sin pensar en la API. Es la misma trampa que el módulo 4 vio en los formularios, ahora con más alcance.

Obliga a quien la usa a hacer tu trabajo. Si la API devuelve editorial: 3 y quien la consume necesita el nombre, tiene que hacer otra petición. Multiplicado por cien libros, es el N+1 de la lección 9 pero al otro lado del cable, y es mucho peor.

La pregunta correcta no es "¿qué tablas tengo?", es "¿qué va a hacer quien use esto?". Con el catálogo, algo así:

Salida en pantalla
- buscar libros por título o autor
- ver la ficha de un libro con su editorial y sus autores
- publicar un libro que estaba en borrador
- dejar una reseña
- ver las reseñas de un libro

De ahí salen los recursos, y salen distintos de las tablas.

Las rutas#

Sustantivos, en plural, en minúsculas y en jerarquía. Es lo de la lección 1, ahora aplicado:

Salida en pantalla
/api/libros/                  lista y creación
/api/libros/42/               uno concreto
/api/libros/42/resenas/       las reseñas de ese libro
/api/editoriales/
/api/autores/

Y los cinco verbos dan la funcionalidad completa sin inventar nombres:

/api/libros//api/libros/42/
GETListaLa ficha
POSTCrea—
PUT—Reemplaza
PATCH—Modifica
DELETE—Borra

Cuatro decisiones que hay que tomar y que la gente toma por accidente:

¿En español o en inglés? Este curso va en español y las rutas van en español, con una regla: sin tildes ni eñes. /api/resenas/ y no /api/reseñas/, porque una dirección con caracteres no ASCII se codifica y aparece como /api/rese%C3%B1as/ en los registros, en las documentaciones y en el código de quien la use. Elige lo que elijas, sé consistente: media API en inglés y media en español es lo peor de los dos mundos.

¿Barra final o no? Django la pone y redirige si falta. Quédate con la de Django y no le des más vueltas; lo único importante es no mezclar.

¿/api/ delante? Sí, o un subdominio. Separa la API de las páginas y permite servirlas distinto, cachearlas distinto y moverlas de sitio.

¿La versión en la dirección? Es la lección 22 y conviene decidirlo ahora, porque añadirla después es el cambio que rompe a todo el mundo. Si hay duda, ponla: /api/v1/.

Las acciones que no encajan#

Este es el problema real del diseño REST, y llega enseguida: publicar un libro no es crear, ni borrar, ni exactamente modificar.

Hay tres soluciones y las tres son legítimas.

Tratarlo como un cambio de campo, que es lo más REST:

Salida en pantalla
PATCH /api/libros/42/     {"estado": "publicado"}

Simple, y no distingue entre "corregir un error de estado" y "publicar", que pueden tener permisos y efectos muy distintos.

Convertir la acción en un recurso. El truco que resuelve casi todos los casos: si la acción tiene sustantivo, ese sustantivo es un recurso.

Salida en pantalla
POST   /api/libros/42/publicacion/     publicar
DELETE /api/libros/42/publicacion/     despublicar

Encaja especialmente bien cuando la acción deja rastro: una publicación tiene fecha y responsable, así que es una cosa y no un evento.

Rendirse y poner un verbo, que es lo que hace todo el mundo, incluido GitHub:

Salida en pantalla
POST /api/libros/42/publicar/

No es REST puro y es clarísimo. DRF lo soporta con un decorador y es la lección 16.

La regla honrada: usa PATCH cuando de verdad sea cambiar un campo, y un verbo cuando sea una operación con reglas propias. Forzar PATCH para algo que manda correos, cobra dinero y cambia cuatro tablas es peor diseño que admitir que es una acción.

Los nombres de los campos#

Menos glamuroso y más importante, porque son los que más se usan.

Consistencia por encima de todo. Elige snake_case —que es lo de Python y lo que DRF genera— y no lo mezcles. Si quien consume la API es JavaScript, preferirá camelCase, y existe djangorestframework-camel-case para convertir en la frontera. Decídelo el primer día.

Los identificadores de relación, con sufijo. editorial_id para el número y editorial para el objeto anidado. Que el mismo nombre signifique un número unas veces y un objeto otras es el error que más confunde a quien la consume.

Nada de abreviaturas. descripcion y no desc. Se escribe una vez y se lee mil.

Los booleanos afirmativos. activo y no no_activo, porque "no_activo": false hay que leerlo dos veces.

Lo que genera un enrutador#

Para cerrar el círculo, esto es lo que DRF construye a partir del diseño. Con un recurso registrado:

Salida en pantalla
SimpleRouter:
  ^libros/$                          name=libro-list
  ^libros/(?P<pk>[^/.]+)/$           name=libro-detail

DefaultRouter:
  ^libros/$                          name=libro-list
  ^libros\.(?P<format>[a-z0-9]+)/?$  name=libro-list
  ^libros/(?P<pk>[^/.]+)/$           name=libro-detail
  ^libros/(?P<pk>[^/.]+)\.(?P<format>...)$
  (raíz)                             name=api-root

Dos direcciones y cinco métodos, que es exactamente la tabla de arriba. Y las diferencias entre los dos enrutadores:

El enrutador completo añade una raíz que lista los recursos disponibles, cosa muy cómoda para explorar y que expone el mapa de tu API a cualquiera. Con una API pública, bien; con una interna, piénsalo.

Y añade sufijos de formato, de modo que /api/libros.json funciona además de /api/libros/?format=json. Es cómodo y duplica todas tus rutas.

Los nombres, libro-list y libro-detail, son los que usarás con reverse en las pruebas de la lección 22. Merece la pena fijarse ahora en que salen del nombre base del recurso.