Se deve incorporar recursos vinculados na API REST

5

Estou criando uma API REST em que os clientes podem consultar mensagens enviadas pelo usuário, assim:

GET http://example.com/api/v1/messages?from=0&to=100

Resposta:

[
    {
        "id": 12345,
        "text": "Hello, world!"
    },
    {
        "id": 12346,
        "text": "Testing, testing"
    },
    ...
]

Agora, preciso incluir o nome do usuário que enviou a mensagem. Posso pensar em 4 maneiras de armazenar essas informações, mas não consigo decidir qual é o melhor:

Opção 1:

[
    {
        "id": 12345,
        "sender_id": 16,
        "text": "Hello, world!"
    }
]

Esse método é o mais eficiente para grande escala - se o cliente consultar a API várias vezes, ele poderá armazenar em cache um mapa de ID do usuário para nome e reutilizá-lo. No entanto, para consultas pontuais, ele dobra a quantidade de chamadas de API que o cliente teria que executar (uma vez para a lista de mensagens e outra para localizar o nome de um determinado ID de usuário).

Opção 2:

[
    {
        "id": 12345,
        "sender_name": "John Smith",
        "text": "Hello, world!"
    }
]

Esse método é mais simples para o consumo do cliente, mas se a API precisar ser alterada para incluir o ID do remetente (por exemplo, vinculando de uma mensagem para outra), precisaria de dois campos "remetente" no objeto de mensagem. sender_id e sender_name , que é essencialmente uma versão pior da opção 3.

Opção 3:

[
    {
        "id": 12345,
        "sender": {
            "id": 16,
            "name": "John Smith"
        },
        "text": "Hello, world!"
    }
]

Essa abordagem incorpora o objeto emissor em cada mensagem, o que o torna preparado para o futuro e requer apenas uma única chamada de API por consulta. No entanto, ele adiciona muitas informações redundantes se houver muitas mensagens e poucos usuários (por exemplo, consultando todas as mensagens enviadas por um único usuário).

Opção 4:

{
    "users": [
        {
            "id": 16,
            "name": "John Smith"
        },
        ...
    ],
    "messages": [
        {
            "id": 12345,
            "sender_id": 16,
            "text": "Hello, world!"
        }
    ]
}

Isso resolve o problema de redundância com # 3, mas adiciona muita complexidade à estrutura do objeto de resposta.

    
por Andrew Sun 17.03.2016 / 21:23
fonte

2 respostas

3

Você provavelmente deve escolher a opção 1, mas fez mais RESTful. Também pode fazer sentido fornecer algo como a opção 3/4. Não há nada que obrigue você a escolher apenas um.

No entanto, o que você deve fazer é substituir os IDs pelos links . Em vez de ter um "sender_id", você deve ter apenas um URI apontando para esse recurso do usuário. Observe como isso significa que não preciso saber para qual URI anexar o "sender_id" ou como formatá-lo. Infelizmente, o JSON não possui um tipo de links. Você vai querer olhar para formatos hipermídia embutidos em JSON , por exemplo. HAL ou JSON-LD.

Agora você está certo de que, se eu quiser buscar o registro, preciso fazer duas solicitações. A boa notícia, porém, é que essas solicitações serão armazenadas em cache pelos mecanismos normal do HTTP. Uma rede de entrega de conteúdo pode até armazenar recursos em cache entre os usuários, reduzindo a carga nos servidores e a latência para os usuários. Naturalmente, não estou impedido de ter um recurso que retorne todos os usuários ou os dados para todos os usuários que receberam uma lista de URIs do usuário para que eu possa solicitações em lote. Você pode considerar o OData como um meio organizado de fornecer esses recursos de consulta, mas certamente pode fazer algo mais simples. Por exemplo, ao retornar as mensagens, você também pode adicionar um link para, digamos, "contatos recentes" que tenham os dados expandidos para todos os usuários na lista atual de mensagens. Observe como eu digo fornecer um link e não adicionar um recurso messages/users/ . Você pode adicionar esse recurso, mas não preciso saber sua estrutura de URI para encontrá-lo. Isso facilita para mim e permite que você altere seus URIs no futuro sem prejudicar os consumidores.

Às vezes, haverá garantias transacionais que você vai querer impor. Por exemplo, no exemplo acima, eu poderia receber as mensagens, solicitar os contatos recentes e essa lista pode ser diferente da lista de URIs de remetente que possuo nas mensagens devido a uma condição de corrida. Nos casos em que é importante garantir esse tipo de atomicidade, você deve incorporar os recursos. Alguns dos formatos hipermídia mencionados acima fornecem um mecanismo para isso. Normalmente, essas situações corresponderão naturalmente a um novo recurso. Lembre-se, você não precisa de uma correspondência 1-1 entre recursos e "entidades" ou qualquer outra coisa. De fato, tal correspondência 1-1 provavelmente significa que você está fazendo algo errado. No geral, porém, você deve tentar minimizar os pontos que precisam de tais garantias transacionais. No exemplo acima, seria desnecessário, pois mesmo que um remetente estivesse faltando na lista de "contatos recentes", eu poderia apenas solicitar suas informações individualmente.

    
por 18.03.2016 / 00:09
fonte
1

Eu acho que depende das necessidades também. Digamos que o aplicativo cliente esteja exibindo uma lista de 100 elementos e que cada mensagem tenha o remetente exibido. Se colocarmos todos os dados necessários no JSON (ou seja, ID do remetente e nome do remetente), faremos apenas uma solicitação. Se houver links para remetentes, temos 101 solicitações. Não me diga que isso é eficiente:)

Mas concordo que os URIs são uma boa prática, por exemplo, quando você implementa um recurso quando o usuário pode clicar no nome do remetente e ver mais informações sobre ele. Então, minha opinião sobre essa estrutura seria a seguinte:

[
    {
        "id": 12345,
        "sender": {
            "link": "www.api.example.com/users/16",
            "name": "John Smith"
        },
        "text": "Hello, world!"
    }
]
    
por 21.10.2016 / 09:38
fonte