É uma boa prática fazer a lista de valores de resposta da API como dicionário?

9

Eu tenho um ponto de extremidade da API que retorna algumas estatísticas. Atualmente, a resposta se parece com:

Opção 1:

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

Mas isso parece um pouco complexo e eu como fazê-lo:

Opção 2:

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

Entendo que a opção 1 é mais fácil de ser estendida, mas menos confortável para os usuários. Que outros problemas posso enfrentar usando uma dessas opções? Ou devo criar uma solução híbrida como:

Opção 3:

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

As chaves "some-stats-key1" e "some-stats-key2" são apenas valores internos e espera-se que o usuário da API as mapeie em nomes legíveis usando a documentação. Todas as chaves são únicas.

A ordem das "estatísticas" não é importante.

O caso de uso típico é apenas para obter todas as estatísticas, combinar chaves com nomes legíveis e mostrar como uma tabela em uma página da web. Mas atualmente não posso dizer se ninguém precisará apenas de uma parte das estatísticas posteriormente.

Existe uma prática recomendada para esse problema?

Yann
fonte
1
Por que exatamente você tem o nome "stats" explicitamente nomeado dentro da resposta? Se você não estiver respondendo com nada além de "stats", solte o wrapper externo e retorne a matriz de pares de valores-chave. Isso pode aproximá-lo da limpeza que você procura.
K. Alan Bates
5
Considere como seus clientes vão desserializar seu JSON em uma estrutura. Por exemplo, se você está consumindo essa API de uma página da Web por AJAX, a opção 1 permite iterar facilmente através dos pares de valores-chave com Array.forEache outras Arrayfunções. Seus outros exemplos adicionarão complexidade extra a operações do tipo array, o que poderia dificultar a vida de seus clientes
Ben Cottrell
As estatísticas do @ K.AlanBates não são a única chave em uma resposta. Atualizada. Obrigado.
Yann
1
A ordem das entradas é importante? A opção 1 preserva o pedido.
Roman Susi
2
Sem sugerir os casos de uso de API mais frequentes, não poderia haver resposta de práticas recomendadas aqui.
Roman Susi

Respostas:

8

Eu optaria pela opção 2. Se o consumidor da API se converter some-stats-key1em algo legível, isso provavelmente significa que ele / ela tem uma lista de valores nos quais está interessado (digamos some-stats-key1e some-stats-key3) e iterará sobre essa lista. Ao escolher um objeto JSON, ele será desserializado como um dicionário / mapa que fornece uma consulta conveniente para o consumidor da API.

Isso será mais complicado com a opção 1, onde o consumidor precisa iterar sobre a matriz JSON ou pré-criar seu próprio dicionário com chaves interessantes.

A opção 3 é um pouco detalhada demais para mim, a duplicação dos nomes das chaves simplesmente não me agrada.

Se a extensibilidade for uma preocupação, você sempre poderá publicar uma v2 da sua API retornando algo como

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

e mantenha a v1 para compatibilidade com versões anteriores. Manter a compatibilidade com versões anteriores em uma única versão da API pode ser uma PITA real, se você não tiver controle completo sobre como a API é consumida. Vi um consumo de uma API da minha 'quebra' quando adicionei um par de valor-chave extra (opcional) (ou seja, não alterei a estrutura).

Glorfindel
fonte
3
-1 para "você sempre pode publicar uma v2 da sua API".
22417 Eric Stein
@ EricStein, obrigado por reservar um tempo para explicar seu voto negativo. Editei minha postagem para indicar por que acho que essa é a melhor opção. Se você ainda não concorda, tudo bem comigo.
Glorfindel
1
O controle de versão de uma API voltada para o público não é trivial ou deve ser feito de ânimo leve. Penso que a sua resposta ignora isso como argumento a favor da sua abordagem preferida.
21417 Eric Stain
IMHO, é (muito) mais fácil do que manter a compatibilidade com versões anteriores com uma única versão.
Glorfindel
Retornar duas versões do mesmo tipo de recurso na mesma resposta é provavelmente a pior ideia que alguém pode ter trabalhando com APIs REST: - /. É isso que você está sugerindo no seu exemplo?
LAIV
7

As duas opções têm as vantagens clássicas da Lista versus Mapa.

1) A lista permite entradas duplicadas e mantém a ordem. Se esses recursos forem importantes, use a Lista, mesmo que seja mais desajeitada.

2) O mapa não permite duplicatas. A manutenção da ordem é possível com um pouco de trabalho extra. A grande vantagem é mais simplicidade no formato dos dados, e a busca por um elemento específico é trivial.

Minha escolha padrão é sempre o mapa mais simples, mas YMMV.

user949300
fonte
3

Quando obtenho dados de uma API, sempre verifico se tudo está como eu esperava. Portanto, meu esforço para processar seus dados consiste na verificação e no processamento real.

No caso 1, tenho que verificar: a. Existe uma matriz. b. Todos os itens da matriz são dicionários. c. Todo dicionário tem uma chave "nome". d. Todos os valores para a chave "nome" são exclusivos.

No caso 3, tenho que verificar: a. Existe um dicionário. b. Todos os valores no dicionário são dicionários. c. Cada dicionário possui uma chave "nome" com um valor que corresponde à chave no dicionário externo. Um pouco melhor.

No caso 2, tenho que verificar: a. Existe um dicionário.

(Claro que tenho que verificar os valores também). Portanto, o seu caso 2 requer a menor quantidade de verificação do meu lado. Na verdade, recebo uma estrutura de dados que é imediatamente utilizável.

O único problema com 2 é que não é extensível. Portanto, em vez de enviar o valor como um número, você pode enviar {value: 10}, que pode ser estendido de forma compatível com versões anteriores.

Redundância é ruim. A única coisa que a redundância atinge é me fazer escrever mais código e me forçar a pensar no que devo fazer se os bits redundantes não concordarem. A versão 2 não possui redundância.

gnasher729
fonte
1

Desde que você solicitou boas práticas para o design da API:

  • Eu nunca retorno uma resposta da API contendo um objeto no nível superior. Todas as chamadas de serviço retornam um conjunto (como uma matriz) e esse conjunto contém elementos (como instâncias do objeto). O número de objetos retornados na matriz é irrelevante para o servidor. Se o cliente precisar determinar o número de itens retornados na resposta, essa carga será imposta pelo cliente. Se um único item for esperado, ele será retornado como um conjunto de unidades
  • Quando estou projetando uma API, não pretendo saber qual tecnologia específica meus clientes de API usarão para consumir a API, mesmo quando eu sei qual tecnologia específica eles provavelmente usarão. Minha responsabilidade é comunicar minhas representações de domínio de forma consistente a todos os consumidores, não de forma conveniente a um consumidor específico.
  • Se existe uma opção estrutural para permitir que a resposta seja composta, essa estrutura tem prioridade sobre as opções que não
  • Esforço-me para evitar criar representações que tenham estruturas imprevisíveis.
  • Se a estrutura de uma representação puder ser prevista, todas as instâncias dentro do conjunto de respostas deverão ter a mesma representação e o conjunto de respostas deverá ser consistente internamente.

Portanto, dadas as estruturas que você propôs, a estrutura que eu implementaria seria semelhante 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
fonte
Você poderia explicar por que segue o ponto 1, talvez citando uma referência ou exemplo de uma estrutura ou de algumas APIs da web conhecidas? No geral, sua resposta parece excessivamente complexa, pelo menos à primeira vista.
user949300
@ user949300 ... hmmm. re:overly complexparece muito simples para mim. Sigo o ponto 1 porque faz com que toda a lógica de serialização mantenha convenções consistentes. O número de itens em um conjunto de respostas é um detalhe de implementação da interface de serviço. Para comunicar itens "3" em um conjunto, esses três itens são agrupados de maneira mais conveniente em uma matriz. "1" é um número.
K. Alan Bates
@ user94900 re: example from a well known web APIgoogle.com
K. Alan Bates
@ user94900 re: example from a frameworkqualquer coisa que fala ANSI SQL
K. Alan Bates