una - ventajas api rest
Diferentes representaciones RESTful del mismo recurso (5)
Mi aplicación tiene un recurso en /foo
. Normalmente, está representado por una carga de respuesta HTTP como esta:
{"a": "some text", "b": "some text", "c": "some text", "d": "some text"}
El cliente no siempre necesita los cuatro miembros de este objeto. ¿Cuál es la forma RESTfully semántica para que el cliente le diga al servidor qué necesita en la representación? por ejemplo, si quiere:
{"a": "some text", "b": "some text", "d": "some text"}
¿Cómo debería GET
? Algunas posibilidades (estoy buscando una corrección si no entiendo el RESTO):
-
GET /foo?sections=a,b,d
.- La cadena de consulta (llamada cadena de consulta después de todo) parece significar "buscar recursos que coincidan con esta condición y decirme sobre ellos", no "representar este recurso de acuerdo con esta personalización".
-
GET /foo/a+b+d
Mi semántica de REST favorita no cubre este problema , debido a su simplicidad.- Rompe la opacidad URI, violando HATEOAS.
- Parece romper la distinción entre los recursos (el único significado de un URI es identificar un recurso) y la representación. Pero eso es discutible porque es coherente con
/widgets
representan una lista presentable de recursos/widget/<id>
, que nunca he tenido un problema.
- Afloje mis limitaciones, responda a
GET /foo/a
, etc. y pida al cliente que haga una solicitud por componente de/foo
que desee.- Multiplica los gastos generales, lo que puede convertirse en una pesadilla si
/foo
tiene cientos de componentes y el cliente necesita 100 de ellos. - Si quiero admitir una representación HTML de
/foo
, tengo que usar Ajax, lo cual es problemático si solo quiero una página HTML única que pueda rastrearse, renderizada por navegadores minimalistas, etc. - Para mantener HATEOAS, también requiere enlaces a esos "sub-recursos" para existir dentro de otras representaciones, probablemente en
/foo
:{"a": {"url": "/foo/a", "content": "some text"}, ...}
- Multiplica los gastos generales, lo que puede convertirse en una pesadilla si
-
GET /foo
,Content-Type: application/json
y{"sections": ["a","b","d"]}
en el cuerpo de la solicitud.- Unbookmarkable y descartable.
- HTTP no define la semántica del cuerpo para
GET
. Es un HTTP legal, pero ¿cómo puedo garantizar que el proxy de un usuario no elimine el cuerpo de una solicitudGET
? - Mi cliente REST no me deja poner un cuerpo en una solicitud
GET
así que no puedo usar eso para las pruebas.
- Un encabezado HTTP personalizado:
Sections-Needed: a,b,d
- Prefiero evitar encabezados personalizados si es posible.
- Unbookmarkable y descartable.
-
POST /foo/requests
,Content-Type: application/json
y{"sections": ["a","b","d"]}
en el cuerpo de la solicitud. Recibe un201
conLocation: /foo/requests/1
. LuegoGET /foo/requests/1
para recibir la representación deseada de/foo
- Clunky; requiere un vaivén y un código de aspecto extraño.
- Unbookmarkable e incableable dado que
/foo/requests/1
es solo un alias que solo se usará una vez y solo se conservará hasta que se solicite.
En realidad, depende de la funcionalidad del recurso. Si, por ejemplo, el recurso representa una entidad:
/customers/5
Aquí el ''5'' representa una identificación del cliente
Respuesta:
{
"id": 5,
"name": "John",
"surename": "Doe",
"marital_status": "single",
"sex": "male",
...
}
Entonces, si lo examinamos de cerca, cada propiedad json en realidad representa un campo del registro en la instancia de recursos del cliente. Supongamos que al consumidor le gustaría obtener una respuesta parcial, es decir, parte de los campos . Podemos verlo como el consumidor quiere tener la capacidad de seleccionar los diversos campos a través de la solicitud, que son interesantes para él, pero no más (con el fin de ahorrar tráfico o rendimiento, si parte de los campos son difíciles de calcular) .
Creo que en esta situación, la API más legible y correcta sería (por ejemplo, obtener solo nombre y surename )
/customers/5?fields=name,surename
Respuesta:
{
"name": "John",
"surename": "Doe"
}
HTTP / 1.1
- si se solicita un nombre de campo ilegal, se devuelve 404 (No encontrado)
- si se solicitan diferentes nombres de campo, se generarán diferentes respuestas, que también se alinean con el almacenamiento en caché.
- Contras: si se solicitan los mismos campos, pero el orden es diferente entre los campos (por ejemplo:
fields=id,name
ofields=name,id
), aunque la respuesta es la misma, esas respuestas se guardarán en caché por separado.
HATEOAS
- En mi opinión, HATEOAS puro no es adecuado para resolver este problema en particular. Porque para lograr eso, necesitas un recurso separado para cada permutación de combinaciones de campo , lo cual es excesivo, ya que está hinchando la API extensamente (digamos que tienes 8 campos en un recurso, necesitarás permutaciones!).
- si modela recursos solo para los campos pero no para todas las permutaciones, tiene implicaciones de rendimiento, por ejemplo, desea que el número de viajes redondos sea mínimo.
He decidido lo siguiente:
Soportando pocas combinaciones de miembros : voy a encontrar un nombre para cada combinación. por ejemplo, si un artículo tiene miembros para autor, fecha y cuerpo, /article/some-slug
devolverá todo y /article/some-slug/meta
simplemente devolverá el autor y la fecha.
Compatible con muchas combinaciones: separaré los nombres de los miembros por guiones: /foo/abc
.
De cualquier manera, devolveré un 404
si la combinación no es compatible.
Restricción arquitectónica
DESCANSO
Identificando recursos
De la definición de REST:
un recurso R es una función de pertenencia que varía temporalmente M R ( t ), que para el tiempo t se correlaciona con un conjunto de entidades, o valores, que son equivalentes. Los valores en el conjunto pueden ser representaciones de recursos y / o identificadores de recursos.
Una representación es un cuerpo HTTP y un identificador es una URL.
Esto es crucial. Un identificador es solo un valor asociado con otros identificadores y representaciones. Eso es distinto del identificador → mapeo de representación. El servidor puede asignar cualquier identificador que desee a cualquier representación, siempre que ambos estén asociados por el mismo recurso.
Depende del desarrollador crear definiciones de recursos que describan razonablemente el negocio pensando en categorías de cosas como "usuarios" y "publicaciones".
HATEOAS
Si realmente me importa HATEOAS perfecto, podría poner un hipervínculo en la representación /foo/members
a /foo/members
, y esa representación solo contendría un hipervínculo a cada combinación de miembros admitida.
HTTP
De la definition de una URL:
El componente de consulta contiene datos no jerárquicos que, junto con los datos en el componente de ruta, sirven para identificar un recurso dentro del alcance del esquema de URI y autoridad de denominación (si existe).
So /foo?sections=a,b,d
y /foo?sections=b
son identificadores distintos. Pero pueden asociarse dentro del mismo recurso mientras se asignan a diferentes representaciones.
El código 404
de HTTP means que el servidor no pudo encontrar nada para asignar la URL, no que la URL no esté asociada a ningún recurso.
Funcionalidad
Ningún navegador o caché tendrá problemas con barras o guiones.
Si a, b, c son propiedad de un recurso como admin para la propiedad de la función, la forma correcta de hacerlo es la primera forma en que sugirió GET /foo?sections=a,b,d
porque en este caso aplicaría una filtrar a la colección foo
. De lo contrario, si a, b y c son un recurso singole de la colección foo
, la forma en que seguiría es hacer una serie de solicitudes GET
/foo/a /foo/b /foo/c
. Este enfoque, como dijiste, tiene una gran carga útil para la solicitud, pero es la forma correcta de seguir el enfoque Restfull. No utilizaría la segunda propuesta hecha por usted porque más char en una url tiene un significado especial.
Otra propuesta es abandonar el uso de GET y POST y crear una acción para la colección foo
como lo hace: /foo/filter
o /foo/selection
o cualquier verbo que represente una acción en la colección. De esta manera, al tener un cuerpo de solicitud postal, puede pasar una lista json del recurso que tendría.
Yo sugeriría la solución querystring (la primera). Sus argumentos contra las otras alternativas son buenos argumentos (y con los que me he encontrado en la práctica al tratar de resolver el mismo problema). En particular, la solución "aflojar las restricciones / responder a las foo/a
" puede funcionar en casos limitados, pero introduce una gran complejidad en una API tanto desde la implementación como desde el consumo y, en mi experiencia, no ha valido la pena el esfuerzo.
Contrastaré débilmente su argumento "parece querer decir" con un ejemplo común: considere el recurso que es una gran lista de objetos ( GET /Customers
). Es perfectamente razonable ubicar estos objetos en la página, y es común usar la cadena de consulta para hacer eso: GET /Customers?offset=100&take=50
como ejemplo. En este caso, la cadena de consulta no se está filtrando en ninguna propiedad del objeto enumerado, sino que proporciona parámetros para una subvista del objeto.
Más concretamente, diría que puede mantener la coherencia y HATEOAS a través de estos criterios para el uso de la cadena de consulta:
- el objeto devuelto debe ser la misma entidad que la devuelta desde la URL sin la cadena de consulta.
- el URI sin la cadena de consulta debería devolver el objeto completo: un superconjunto de cualquier vista disponible con una cadena de consulta en el mismo Uri. Entonces, si almacena en caché el resultado del Uri no decorado, sabrá que tiene la entidad completa.
- el resultado devuelto para una cadena de consulta determinada debe ser determinista, de modo que los Uris con cadenas de consulta sean fácilmente almacenables en caché
Sin embargo, qué devolver para estos Uris a veces puede plantear preguntas más complejas:
- devolver un tipo de entidad diferente para Uris que difiera solo por la cadena de consulta podría ser indeseable (
/foo
es una entidad perofoo/a
es una cadena); la alternativa es devolver una entidad parcialmente poblada - si utiliza diferentes tipos de entidades para subconsultas, si su
/foo
no tiene unaa
, un estado404
es engañoso (/foo
sí existe!), pero una respuesta vacía puede ser igualmente confusa - devolver una entidad parcialmente poblada puede ser indeseable, pero devolver parte de una entidad puede no ser posible, o puede ser más confuso
- devolver una entidad parcialmente poblada puede no ser posible si tiene un esquema fuerte (si
a
es obligatorio pero el cliente solo solicitab
, se le obliga a devolver un valor no deseado paraa
, o un objeto no válido)
En el pasado, he tratado de resolver esto definiendo "vistas" con nombre específico de entidades requeridas, y permitiendo una cadena de consulta como ?view=summary
o ?view=totalsOnly
- limitando el número de permutaciones. Esto también permite la definición de un subconjunto de la entidad que "tiene sentido" para el consumidor del servicio, y puede documentarse.
En última instancia, creo que esto se reduce a una cuestión de consistencia más que nada: puede cumplir con la orientación de HATEOAS utilizando la cadena de consulta de forma relativamente fácil, pero las elecciones que haga deben ser consistentes en toda la API y, diría, bien documentadas.
puede usar un segundo tipo de medio de proveedor en la aplicación de encabezado de solicitud / vnd.com.mycompany.resource.rep2, pero no puede marcarlo, los parámetros de consulta no son almacenables en caché (/ foo? sections = a, b, c ) podría echar un vistazo a los parámetros de la matriz, sin embargo, con respecto a esta pregunta, deberían ser parámetros de la matriz URL en caché frente a los parámetros de solicitud