¿Es una buena práctica hacer una lista de valores de respuesta API como diccionario?

9

Tengo un punto final de API que devuelve algunas estadísticas. Actualmente la respuesta se ve así:

Opción 1:

{
    "stats": [
                {
                    "name": "some-stats-key1",
                    "value": 10
                },
                {
                    "name": "some-stats-key2",
                    "value": 20
                }
            ],
    ... other keys
}

Pero esto parece un poco complejo y sé cómo hacerlo:

Opcion 2:

{
    "stats": {
                "some-stats-key1": 10,
                "some-stats-key2": 20
            }
    ... other keys
}

Entiendo que la Opción 1 es más fácil de extender, pero menos cómoda para los usuarios. ¿Qué otros problemas puedo enfrentar usando una de estas opciones? ¿O debería hacer una solución híbrida como:

Opcion 3:

{
    "stats": {
                "some-stats-key1": {
                                        "name": "some-stats-key1",
                                        "value": 10
                                    },
                "some-stats-key2": {
                                        "name": "some-stats-key2",
                                        "value": 20
                                    },
            },
    ... other keys
}

Las claves "some-stats-key1" y "some-stats-key2" son solo valores internos y se espera que el usuario de la API los asigne a nombres legibles utilizando la documentación. Todas las llaves son únicas.

El orden de las "estadísticas" no es importante.

El caso de uso típico es solo obtener todas las estadísticas, hacer coincidir las claves con nombres legibles y mostrarlas como una tabla en una página web. Pero actualmente no puedo decir si nadie necesitará solo una parte de las estadísticas más adelante.

¿Existe una mejor práctica para este problema?

Yann
fuente
1
¿Por qué exactamente tiene el nombre "estadísticas" explícitamente nombrado dentro de la respuesta? Si no responde con otra cosa que no sea "estadísticas", suelte el contenedor externo y simplemente devuelva la matriz de pares clave-valor. Eso podría acercarlo a la limpieza que busca.
K. Alan Bates el
55
Considere cómo sus clientes van a deserializar su JSON en una estructura. Por ejemplo, si está consumiendo esta API desde una página web a través de AJAX, la opción 1 le permite iterar fácilmente a través de los pares clave-valor con Array.forEachy otras Arrayfunciones. Sus otros ejemplos agregarán complejidad adicional a las operaciones de tipo matriz, lo que podría estar dificultando la vida de sus clientes
Ben Cottrell
@ K.AlanBates stats no es la única clave en una respuesta. Actualizado. Gracias.
Yann
1
¿Es importante el orden de las entradas? La opción 1 conserva el orden.
Roman Susi
2
Sin insinuar los casos de uso de API más frecuentes, no podría haber una respuesta de mejores prácticas aquí.
Roman Susi

Respuestas:

8

Iría por la opción 2. Si el consumidor de API se convertirá some-stats-key1en algo legible, eso probablemente significa que él / ella tiene una lista de valores en los que está interesado (digamos, some-stats-key1y some-stats-key3), e iterará sobre esa lista. Al elegir un objeto JSON, se deserializará como un diccionario / mapa que proporciona una búsqueda conveniente para el consumidor de la API.

Esto será más engorroso con la opción 1, donde el consumidor necesita iterar sobre la matriz JSON o crear previamente su propio diccionario con claves interesantes.

La opción 3 es demasiado detallada para mí, la duplicación de los nombres clave simplemente no me atrae.

Si la extensibilidad es una preocupación, siempre puede publicar una v2 de su API que devuelva algo como

"stats": {
    "some-stats-key1": { "value": 10, "error-margin": 0.1 },
    "some-stats-key2": { "value": 20, "error-margin": 0.2 }
}

y mantenga el v1 para compatibilidad con versiones anteriores. Mantener la compatibilidad con versiones anteriores dentro de una única versión de la API puede ser un verdadero PITA si no tiene un control completo sobre cómo se consume la API. He visto un consumo de una 'ruptura' de API mía cuando acabo de agregar un par clave-valor adicional (opcional) (es decir, sin cambiar la estructura).

Glorfindel
fuente
3
-1 para "siempre puedes publicar una v2 de tu API".
Eric Stein
@EricStein gracias por tomarse el tiempo para explicar su voto negativo. Edité mi publicación para indicar por qué creo que esta es la mejor opción. Si todavía no estás de acuerdo, bien conmigo.
Glorfindel
1
El control de versiones de una API pública no es trivial o debe hacerse a la ligera. Creo que su respuesta descarta eso como un argumento a favor de su enfoque preferido.
Eric Stein
En mi humilde opinión, es (mucho) más fácil que mantener la compatibilidad con versiones anteriores.
Glorfindel
Devolver dos versiones del mismo tipo de recurso en la misma respuesta es probablemente la peor idea que alguien puede tener trabajando con las API REST: - /. ¿Es eso lo que estás sugiriendo en tu ejemplo?
Laiv
7

Las dos opciones tienen las ventajas clásicas de List vs. Map.

1) La Lista permite entradas duplicadas y mantiene el orden. Si estas características son importantes, use la Lista, aunque sea más complicada.

2) El mapa no permite duplicados. Mantener el orden es posible con un poco de trabajo extra. La gran ventaja es una mayor simplicidad en el formato de datos, y la búsqueda de un elemento en particular es trivial.

Mi opción predeterminada es siempre el Mapa más simple, pero YMMV.

user949300
fuente
3

Cuando obtengo datos de una API, siempre verifico que todo esté como lo esperaba. Por lo tanto, mi esfuerzo para procesar sus datos consiste en la verificación y el procesamiento real.

En el caso 1 tengo que verificar: a. Hay una matriz si. Todos los elementos de la matriz son diccionarios. C. Cada diccionario tiene una clave "nombre". re. Todos los valores para la clave "nombre" son únicos.

En el caso 3 tengo que verificar: a. Hay un diccionario si. Todos los valores en el diccionario son diccionarios. C. Cada diccionario tiene un "nombre" clave con un valor que coincide con la clave en el diccionario externo. Ligeramente mejor.

En el caso 2 tengo que verificar: a. Hay un diccionario

(Por supuesto, también tengo que verificar los valores). Por lo tanto, su caso 2 requiere la menor cantidad de control de mi parte. De hecho, obtengo una estructura de datos que se puede usar de inmediato.

El único problema con 2 es que no es extensible. Por lo tanto, en lugar de enviar el valor como un número, puede enviar {valor: 10} que luego se puede extender de una manera compatible con versiones anteriores.

La redundancia es mala. Lo único que logra la redundancia es hacerme escribir más código y obligarme a pensar qué debo hacer si los bits redundantes no están de acuerdo. La versión 2 no tiene redundancia.

gnasher729
fuente
1

Como solicitó buenas prácticas para el diseño de API:

  • Nunca devuelvo una respuesta de API que contiene un objeto en el nivel superior. Todas las llamadas de servicio devuelven un conjunto (como una matriz), y ese conjunto contiene elementos (como instancias de objeto). El número de objetos devueltos en la matriz es irrelevante para el servidor. Si el cliente necesita hacer una determinación a partir del número de artículos devueltos en la respuesta, esa carga es la que debe cumplir el cliente. Si se espera un solo artículo, se devuelve como un conjunto de unidades
  • Cuando estoy diseñando una API, no pretendo saber qué tecnología específica utilizarán mis clientes de API para consumir dicha API, incluso cuando sé qué tecnología específica es más probable que usen. Mi responsabilidad es comunicar mis representaciones de dominio de manera consistente a todos los consumidores, no convenientemente a un consumidor en particular.
  • Si existe una opción estructural para permitir que se componga la respuesta, esta estructura tiene prioridad sobre las opciones que no
  • Me esfuerzo por evitar crear representaciones que tengan estructuras impredecibles.
  • Si se puede predecir la estructura de una representación, entonces todas las instancias dentro del conjunto de respuestas deben ser de la misma representación y el conjunto de respuestas debe ser internamente consistente.

Entonces, dadas las estructuras que ha propuesto, la estructura que implementaría sería similar a esta

[
   { /* returns only the 'common' metrics */
      "stats": [
                  {"key":"some-metric1","value":10},
                  {"key":"some-metric2","value":20}
               ]
   },
   { /* returns an optional metric in addition to the "common" metrics */
      "stats": [
                  {"key":"some-metric1","value":15},
                  {"key":"some-metric2","value":5},
                  {"key":"some-optional-metric", "value":42}
               ]
   },
   { /*returns the 'common' metrics as well as 2 candidates for "foo-bar" */
      "stats": [
                  {"key":"some-metric1", "value": 5},
                  {"key":"some-metric2", "value": 10},
                  {"key":"foo-bar-candidate", "value": 7},
                  {"key":"foo-bar-candidate", "value": 11}
               ]
   }
]
K. Alan Bates
fuente
¿Podría explicar por qué sigue el punto 1, quizás citando una referencia o ejemplo de un marco o algunas API web bien conocidas? En general, su respuesta parece demasiado compleja, al menos a primera vista.
user949300
@ user949300 ... hmmm. re:overly complexme parece muy simple. Sigo el punto 1 porque hace que toda la lógica de serialización mantenga convenciones consistentes. El número de elementos en un conjunto de respuestas es un detalle de implementación de la interfaz de servicio. Para comunicar "3" elementos en un conjunto, esos tres elementos se envuelven más convenientemente en una matriz. "1" es un número.
K. Alan Bates
@ user94900 re: example from a well known web APIgoogle.com
K. Alan Bates
@ user94900 re: example from a frameworktodo lo que habla ANSI SQL
K. Alan Bates