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.
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:
Editorial nombre, país
Autor nombre, nacimiento
Libro título, ISBN, precio, páginas, estado, editorial, autores
Reseña libro, autor, puntuación, textoAhí 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í:
- 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 libroDe 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:
/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/ | |
|---|---|---|
GET | Lista | La ficha |
POST | Crea | — |
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:
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.
POST /api/libros/42/publicacion/ publicar
DELETE /api/libros/42/publicacion/ despublicarEncaja 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:
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:
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-rootDos 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.