API REST Práctica recomendada: cómo aceptar la lista de valores de parámetros como entrada [cerrado]

410

Estamos lanzando una nueva API REST y quería información de la comunidad sobre las mejores prácticas sobre cómo deberíamos formatear los parámetros de entrada:

En este momento, nuestra API está muy centrada en JSON (solo devuelve JSON). El debate sobre si queremos / necesitamos devolver XML es un tema aparte.

Como nuestra salida API está centrada en JSON, hemos estado siguiendo un camino en el que nuestras entradas están un poco centradas en JSON y he estado pensando que puede ser conveniente para algunos pero extraño en general.

Por ejemplo, para obtener algunos detalles del producto donde se pueden extraer varios productos a la vez, actualmente tenemos:

http://our.api.com/Product?id=["101404","7267261"]

¿Deberíamos simplificar esto como:

http://our.api.com/Product?id=101404,7267261

¿O es útil tener la entrada JSON? Más de un dolor?

Es posible que queramos aceptar ambos estilos, pero ¿esa flexibilidad realmente causa más confusión y dolores de cabeza (mantenibilidad, documentación, etc.)?

Un caso más complejo es cuando queremos ofrecer entradas más complejas. Por ejemplo, si queremos permitir múltiples filtros en la búsqueda:

http://our.api.com/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}

No necesariamente queremos poner los tipos de filtro (por ejemplo, productType y color) como nombres de solicitud como este:

http://our.api.com/Search?term=pumas&productType=["Clothing","Bags"]&color=["Black","Red"]

Porque queríamos agrupar todas las entradas de filtro juntas.

Al final, ¿esto realmente importa? Es probable que haya tantas utilidades JSON por ahí que el tipo de entrada simplemente no importa tanto.

Sé que nuestros clientes de JavaScript que realizan llamadas AJAX a la API pueden apreciar las entradas JSON para facilitarles la vida.

que pasa
fuente

Respuestas:

341

Un paso atrás

En primer lugar, REST describe un URI como una ID universalmente única. Demasiadas personas quedan atrapadas en la estructura de los URI y cuáles son más "relajantes" que otros. Este argumento es tan ridículo como decir que nombrar a alguien "Bob" es mejor que nombrarlo "Joe": ambos nombres hacen el trabajo de "identificar a una persona". Un URI no es más que un nombre universalmente único .

Entonces, a los ojos de REST, discutir si ?id=["101404","7267261"]es más tranquilo ?id=101404,7267261o \Product\101404,7267261algo inútil.

Ahora, dicho esto, muchas veces la forma en que se construyen los URI puede servir como un buen indicador para otros problemas en un servicio RESTful. Hay un par de banderas rojas en sus URI y preguntas en general.

Sugerencias

  1. Múltiples URI para el mismo recurso y Content-Location

    Es posible que queramos aceptar ambos estilos, pero ¿esa flexibilidad realmente causa más confusión y dolores de cabeza (mantenibilidad, documentación, etc.)?

    Los URI identifican recursos. Cada recurso debe tener un URI canónico. Esto no significa que no pueda hacer que dos URI apunten al mismo recurso, pero existen formas bien definidas de hacerlo. Si decide utilizar tanto el JSON como los formatos basados ​​en listas (o cualquier otro formato), debe decidir cuál de estos formatos es el URI canónico principal . Todas las respuestas a otros URI que apuntan al mismo "recurso" deben incluir el Content-Locationencabezado .

    Siguiendo con la analogía del nombre, tener múltiples URI es como tener apodos para las personas. Es perfectamente aceptable y muchas veces bastante útil, sin embargo, si estoy usando un apodo, probablemente todavía quiera saber su nombre completo, la forma "oficial" de referirme a esa persona. De esta manera, cuando alguien menciona a alguien por su nombre completo, "Nichloas Telsa", sé que están hablando de la misma persona a la que me refiero como "Nick".

  2. "Buscar" en su URI

    Un caso más complejo es cuando queremos ofrecer entradas más complejas. Por ejemplo, si queremos permitir múltiples filtros en la búsqueda ...

    Una regla general es que, si su URI contiene un verbo, puede ser una indicación de que algo está mal. Los URI identifican un recurso, sin embargo, no deben indicar lo que le estamos haciendo a ese recurso. Ese es el trabajo de HTTP o, en términos relajantes, nuestra "interfaz uniforme".

    Para vencer la analogía del nombre, usar un verbo en un URI es como cambiar el nombre de alguien cuando quieres interactuar con él. Si estoy interactuando con Bob, el nombre de Bob no se convierte en "BobHi" cuando quiero saludarlo. Del mismo modo, cuando queremos "buscar" Productos, nuestra estructura de URI no debería cambiar de "/ Producto / ..." a "/ Buscar / ...".

Respondiendo su pregunta inicial

  1. Con respecto a ["101404","7267261"]vs 101404,7267261: Mi sugerencia aquí es evitar la sintaxis JSON por simplicidad (es decir, no requiere que sus usuarios hagan codificación de URL cuando realmente no es necesario). Hará que su API sea un poco más útil. Mejor aún, como lo han recomendado otros, elija el application/x-www-form-urlencodedformato estándar , ya que probablemente sea más familiar para sus usuarios finales (por ejemplo ?id[]=101404&id[]=7267261). Puede que no sea "bonito", pero los URI bonitos no necesariamente significan URI utilizables. Sin embargo, para reiterar mi punto inicial, en última instancia, cuando se habla de REST, no importa. No te detengas demasiado en ello.

  2. Su ejemplo de URI de búsqueda compleja puede resolverse de la misma manera que su ejemplo de producto. Recomiendo volver al application/x-www-form-urlencodedformato ya que ya es un estándar con el que muchos están familiarizados. Además, recomendaría fusionar los dos.

Tu URI ...

/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}    

Su URI después de ser codificado en URI ...

/Search?term=pumas&filters=%7B%22productType%22%3A%5B%22Clothing%22%2C%22Bags%22%5D%2C%22color%22%3A%5B%22Black%22%2C%22Red%22%5D%7D

Se puede transformar en ...

/Product?term=pumas&productType[]=Clothing&productType[]=Bags&color[]=Black&color[]=Red

Además de evitar el requisito de codificación de URL y hacer que las cosas se vean un poco más estándar, ahora homogeneiza un poco la API. El usuario sabe que si desea recuperar un Producto o Lista de Productos (ambos se consideran un "recurso" único en términos RESTful), están interesados ​​en los /Product/...URI.

nategood
fuente
67
Quería hacer un seguimiento y tener en cuenta que la []sintaxis no siempre es compatible (y a pesar de ser común, incluso puede violar las especificaciones de URI). Algunos servidores HTTP y lenguajes de programación preferirán simplemente repetir el nombre (por ejemplo productType=value1&productType=value2).
nategood
1
La pregunta inicial con esta consulta ... "/ Search? Term = pumas & filters = {" productType ": [" Ropa "," Bolsas "]," color ": [" Negro "," Rojo "]}" se traduce en ... . (productType == clothing || productType == bags) && (color == black || color == red) PERO SU SOLUCIÓN: / Product? term = pumas & productType [] = Clothing & productType [] = Bags & color [] = Black & color [] = Rojo parece traducir a ... O bien (productType == ropa || productType == bolsos || color == negro || color == rojo) o bien (productType == ropa && productType == bolsos && color == negro && color == rojo) Lo cual parece ser un poco diferente para mí. ¿O entendí mal?
Thomas Cheng
2
¿Qué pasa con las entradas en la solicitud posterior? Quería saber si estamos actualizando un recurso, entonces es una mala práctica enviar la consulta / filtro y los datos en el cuerpo en un formato estándar. por ej. si quiero cambiar los datos relacionados con el usuario que utiliza el API /user/y en el cuerpo, voy a enviar { q:{}, d: {} }con qcomo consulta por con el usuario será consultado en la base de datos y dque los datos modificados.
molécula
1
¿Qué haces cuando la lista puede ser muy grande? El URI tiene una longitud limitada según el navegador. Por lo general, cambié a una solicitud de publicación y envié la lista en el cuerpo. ¿Alguna sugerencia allí?
Troy Cosentino
44
Tendría que ser MUY grande (consulte stackoverflow.com/questions/417142/… ), pero sí, en los casos más extremos, es posible que deba usar el cuerpo de la solicitud. PUBLICAR consultas para la recuperación de datos es una de esas cosas que a los RESTafarians les encanta debatir.
nategood
234

La forma estándar de pasar una lista de valores como parámetros de URL es repetirlos:

http://our.api.com/Product?id=101404&id=7267261

La mayoría del código del servidor interpretará esto como una lista de valores, aunque muchos tienen simplificaciones de un solo valor, por lo que es posible que tenga que buscar.

Los valores delimitados también están bien.

Si necesita enviar JSON al servidor, no me gusta verlo en la URL (que es un formato diferente). En particular, las URL tienen una limitación de tamaño (en la práctica, si no en teoría).

La forma en que he visto a algunos hacer una consulta complicada RESTfully es en dos pasos:

  1. POST sus requisitos de consulta, recibiendo una identificación (esencialmente creando un recurso de criterios de búsqueda)
  2. GET la búsqueda, haciendo referencia a la ID anterior
  3. opcionalmente BORRAR los requisitos de consulta si es necesario, pero tenga en cuenta que estos requisitos están disponibles para su reutilización.
Kathy Van Stone
fuente
8
Gracias kathy Creo que estoy contigo y realmente no me gusta ver a JSON en la URL también. Sin embargo, no soy fanático de hacer una publicación para una búsqueda que es una operación GET inherente. ¿Puedes señalar un ejemplo de esto?
whatupwilly
1
Si las consultas pueden funcionar como parámetros simples, hazlo. La fuente era de la lista de correo de descanso y discusión: tech.groups.yahoo.com/group/rest-discuss/message/11578
Kathy Van Stone
2
Si solo desea mostrar dos recursos, la respuesta de James Westgate es más típica
Kathy Van Stone el
Esta es la respuesta correcta. En un futuro cercano, estoy seguro de que veremos algunos filter = id en (a, b, c, ...) compatibles con OData o algo así.
Bart Calixto
Así es como funciona Akka HTTP
Joan
20

Primero:

Creo que puedes hacerlo de 2 maneras

http://our.api.com/Product/<id> : si solo quieres un disco

http://our.api.com/Product : si quieres todos los registros

http://our.api.com/Product/<id1>,<id2> : como sugirió James puede ser una opción, ya que lo que viene después de la etiqueta Producto es un parámetro

O el que más me gusta es:

Puede utilizar la propiedad Hypermedia como el motor del estado de aplicación (HATEOAS) de un RestFul WS y hacer una llamada http://our.api.com/Productque debería devolver las URL equivalentes http://our.api.com/Product/<id>y llamarlas después de esto.

Segundo

Cuando tienes que hacer consultas sobre las llamadas url. Sugeriría usar HATEOAS nuevamente.

1) Haz una llamada a http://our.api.com/term/pumas/productType/clothing/color/black

2) Haz una llamada a http://our.api.com/term/pumas/productType/clothing,bags/color/black,red

3) (Usando HATEOAS) Realice una llamada a ` http://our.api.com/term/pumas/productType/ -> reciba las urls todas las urls posibles de ropa -> llame a las que desee (ropa y bolsos) - > recibe las posibles URL de color -> llama a las que quieras

Diego Dias
fuente
1
Me pusieron en una situación similar hace unos días, teniendo que ajustar una API de descanso (HATEOAS) para obtener una lista filtrada (grande) de objetos y simplemente elegí tu segunda solución. ¿No es un poco excesivo recordar la API una y otra vez?
Samson
Realmente depende de su sistema ... Si es simple con pocas "opciones", probablemente debería ser excesivo. Sin embargo, si tiene algunas listas realmente grandes, puede ser realmente problemático hacerlo todo en una gran llamada, además, si su API es pública, puede ser complicado para los usuarios (si es privada, debería ser más fácil ... solo enseña a los usuarios que conoces). Como alternativa, podría implementar tanto el estilo, el HATEOAS como una llamada de "matriz no reparadora" para usuarios avanzados
Diego Dias el
Estoy construyendo un servicio web de API relajante en rieles y necesito seguir la misma estructura de URL que la anterior ( our.api.com/term/pumas/productType/clothing/color/black ). Pero no estoy seguro de cómo configurar las rutas en consecuencia.
Rubyist
¿son término, tipo de producto y color sus controladores? Si es así, solo necesita hacer: recursos: término hacer recursos: producto Tipo hacer recursos: final de color final
Diego Dias
productType y color son los parámetros. Entonces, los parámetros de productType son prendas de vestir y los parámetros de prendas de vestir son negros
rubyist
12

Es posible que desee ver RFC 6570 . Esta especificación de plantilla de URI muestra muchos ejemplos de cómo las URL pueden contener parámetros.

Darrel Miller
fuente
1
La Sección 3.2.8 parece ser lo que es aplicable. Aunque vale la pena señalar que esto es solo un estándar propuesto y no parece haberse movido más allá de ese punto.
Mike Post
3
@MikePost Ahora que IETF ha pasado a un proceso de madurez de dos pasos para documentos de "seguimiento de estándares", espero que 6570 permanezca así durante unos años más antes de pasar a un "Estándar de Internet". tools.ietf.org/html/rfc6410 La especificación es extremadamente estable, tiene muchas implementaciones y es ampliamente utilizada.
Darrel Miller
Ah, no estaba al tanto de ese cambio. (O, TIL IETF ahora es más razonable.) ¡Gracias!
Mike Post
8

Primer caso:

Una búsqueda de producto normal se vería así

http://our.api.com/product/1

Así que estoy pensando que la mejor práctica sería que hagas esto

http://our.api.com/Product/101404,7267261

Segundo caso

Busque con parámetros de cadena de consulta, bien así. Me sentiría tentado a combinar términos con AND y OR en lugar de usar[] .

PD: Esto puede ser subjetivo, así que haz lo que te haga sentir cómodo.

La razón para poner los datos en la url es para que el enlace se pueda pegar en un sitio / compartir entre los usuarios. Si esto no es un problema, utilice un JSON / POST en su lugar.

EDITAR: en la reflexión, creo que este enfoque se adapta a una entidad con una clave compuesta, pero no una consulta para múltiples entidades.

James Westgate
fuente
3
Por supuesto, en ambos ejemplos, el seguimiento /no debería estar allí ya que el URI aborda un recurso, no una colección.
Lawrence Dol
2
Siempre pensé en los verbos HTTP, en un uso REST era para hacer acciones específicas, y esta era la línea de guía: OBTENER: recuperar / leer objeto, POST crear objeto, PONER actualizar objeto existente y ELIMINAR eliminar un objeto. Entonces no usaría una POST para recuperar algo. Si quiero una lista de objetos en particular (filtro), haría un GET con una lista en los parámetros de URL (separados por una coma parece bueno)
Alex
1

Me pondré del lado de la respuesta de nategood, ya que está completa y parecía satisfacer sus necesidades. Sin embargo, me gustaría agregar un comentario sobre la identificación de múltiples recursos (1 o más) de esa manera:

http://our.api.com/Product/101404,7267261

Al hacerlo, usted:

Complejice a los clientes obligándolos a interpretar su respuesta como una matriz, lo que para mí es intuitivo si hago la siguiente solicitud:http://our.api.com/Product/101404

Cree API redundantes con una API para obtener todos los productos y la anterior para obtener 1 o más. Dado que no debe mostrar más de 1 página de detalles a un usuario por el bien de UX, creo que tener más de 1 ID sería inútil y se utilizaría exclusivamente para filtrar los productos.

Puede que no sea tan problemático, pero tendrá que manejar esto usted mismo del lado del servidor devolviendo una sola entidad (verificando si su respuesta contiene una o más) o dejar que los clientes la administren.

Ejemplo

Quiero pedir un libro de Amazing . Sé exactamente qué libro es y lo veo en la lista cuando navego por los libros de Terror:

  1. 10 000 líneas increíbles, 0 pruebas increíbles
  2. El regreso del monstruo asombroso
  3. Dupliquemos un código increíble
  4. El asombroso principio del fin

Después de seleccionar el segundo libro, soy redirigido a una página que detalla la parte del libro de una lista:

--------------------------------------------
Book #1
--------------------------------------------
    Title: The return of the amazing monster
    Summary:
    Pages:
    Publisher:
--------------------------------------------

¿O en una página que me da todos los detalles de ese libro solamente?

---------------------------------
The return of the amazing monster
---------------------------------
Summary:
Pages:
Publisher:
---------------------------------

Mi opinión

Sugeriría usar la ID en la variable de ruta cuando se garantiza la unicidad al obtener los detalles de este recurso. Por ejemplo, las API a continuación sugieren varias formas de obtener los detalles de un recurso específico (suponiendo que un producto tenga un ID único y una especificación para ese producto tenga un nombre único y que pueda navegar de arriba hacia abajo):

/products/{id}
/products/{id}/specs/{name}

En el momento en que necesite más de 1 recurso, sugeriría filtrar de una colección más grande. Por el mismo ejemplo:

/products?ids=

Por supuesto, esta es mi opinión, ya que no se impone.

gumol
fuente