Design de API de descanso - Trabalhar com IDs ou cadeias literais?

8

Ao projetar um serviço da Web RESTful, a API deve ser projetada para funcionar com o ID de Strings para valores passados ​​entre o servidor?

Aqui está um exemplo: digamos que eu tenho um recurso Employee, que possui atributos de status e de gênero. No banco de dados, Status e Gênero, separa as tabelas e, assim, separa o objeto Domínio, cada um com seu próprio identificador.

Digamos que a solicitação do cliente / funcionário / 1. Não servidor pode retornar algo como estes ....

Caso 1:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "id": 1,
        "gender": "FEMALE"
    },
    "status": {
        "id": 3,
        "status": "FULL_TIME"
    }
}

Caso 2:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "FEMALE",
    "status": "FULL_TIME"
}

Caso 3:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "genderId": 1,
    "statusId": 3
}

O caso 3 parece fazer menos sentido, pois o cliente não tem idéia do que é genderId 1, a menos que ele se vire e faça outra chamada ao servidor para obter esses dados.

No entanto, agora digamos que o cliente esteja atualizando o usuário através de:

PUT /employee/1

A carga útil da solicitação deve usar os IDs ou uma string? De qualquer forma, o back-end precisa procurá-los para garantir que sejam válidos, mas é melhor trabalhar com IDs sobre Strings.

jkratz55
fonte

Respostas:

3

Digamos que eu tenho um recurso Employee, que possui atributos de status e de gênero. No banco de dados, Status e Gênero, separa as tabelas e, assim, separa o objeto Domínio, cada um com seu próprio identificador.

Suas representações de API não devem ser fortemente acopladas aos seus detalhes de implementação. Eu diria que derivar suas representações de API dos detalhes de sua implementação é exatamente ao contrário.

Pense Adapter Patternno livro da Gangue dos Quatro . As mensagens da web são as de um repositório de documentos. Seu objetivo na criação de uma API é produzir os documentos que seus consumidores desejam, enquanto os isola dos detalhes básicos da produção desses documentos.

A motivação para fazer isso é que você pode alterar os detalhes da implementação a qualquer momento, com a certeza de que, contanto que você não altere as representações retornadas, seus clientes não quebrarão.

Além disso, lembre-se de que um único recurso lógico pode ter muitas representações, apenas algumas das quais suportam modificação.

digamos que o cliente esteja atualizando o usuário

Como consumidor, com qual representação você deseja trabalhar? Meu palpite é que o mais próximo é

{
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "FEMALE",
    "status": "FULL_TIME"
}

Se eu colocar essa representação em um local que você especificar, você realmente deve poder resolver o resto.

Se você estivesse criando representações para as máquinas usarem, provavelmente desejaria menos ambiguidade na ortografia

{
    "https://schema.org/givenName": "Jane",
    "https://schema.org/familyName": "Doe",
    "active": true,
    "https://schema.org/gender": "https://schema.org/Female",
    "https://schema.org/employmentType": "FULL_TIME"
}

Mesmo recurso lógico, duas representações diferentes. Cavalos para cursos.

VoiceOfUnreason
fonte
1

O caso 1 e o caso 2 parecem bons. A escolha pode ser prevista pela maneira como você organiza seu modelo de domínio.

Você refletiu as tabelas Empregado, Gênero e Status no domínio (usando ORM, suponho). Cada uma dessas classes neste modelo específico é uma entidade que possui um identificador próprio . A exposição adicional de todo o modelo via API REST parece lógica e se encaixa no Caso 1.

Como alternativa, você pode seguir os princípios do DDD, que prestam muita atenção às diferenças entre entidades e objetos de valor . Desse ponto de vista, Employee é uma entidade (com ID) e Gender e Status podem ser bons candidatos para se tornarem objetos de valor (incorporados à entidade Employee; sem identificadores). Isso se encaixa no caso 2.

Concordo plenamente com você que o Caso 3 é proibido.

Serhii Shushliapin
fonte
1
A menos que haja uma razão convincente, eu não associaria fortemente uma API de serviço da web a um design de banco de dados. APIs e bancos de dados têm clientes diferentes com necessidades diferentes.
Eric Stein
Concordo plenamente. Essa é apenas a minha observação do design da API dos autores (Gender e State expõe ids). Seguindo os princípios do DDD, eu os projetaria como objetos de valor no meu Modelo de Domínio e, como resultado, não exporia seus identificadores por meio da API REST (esse é o caso 2).
Serhii Shushliapin 27/02
1

O caso 2 é a única opção real. Você já apontou os problemas do Caso 3. O Caso 1 fornece informações de que o cliente da API não se importa (os IDs internos para status, por exemplo) e exige que o cliente conheça aqueles para construir uma solicitação PUT . Sim, a solicitação PUT é um pouco mais concisa se puder usar os IDs em vez das seqüências completas, mas especificar "FULL_TIME" ou "PART_TIME" é o que o cliente sabe, não que eles tenham alguns IDs arbitrários no banco de dados .

Obviamente, você pode documentar os IDs na documentação da API, mas é igualmente fácil documentar os valores válidos que as seqüências de caracteres têm permissão e provavelmente mais claras.

David Conrad
fonte
2
Você deve observar que isso significa que um gênero ou status nunca pode ser renomeado. Se os clientes estiverem trabalhando apenas com o nome, esse nome será efetivamente o identificador exclusivo. Se os clientes precisam começar a usar um ID como identificador exclusivo, essa é uma mudança inédita.
Eric Stein
0

Dados enumerados como os que você tem aqui são altamente armazenáveis ​​em cache. Use links em vez de objetos. Use cabeçalhos de cache para permitir que os clientes armazenem em cache gêneros e status localmente, por 24 horas. Somente a primeira chamada do dia sai da máquina do cliente. Provavelmente, você também pode configurar o cache para permitir que servidores intermediários retenham as informações, portanto, algumas solicitações de clientes nem chegam ao seu servidor.

GET /employees/1
{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "/genders/1",
    "status": "/statuses/3"
}

// clients populate their dropdown with
GET /genders
[
    {"id":1, "gender":"FEMALE"},
    {"id":2, "gender":"MALE"},
    ...
]

// clients look up an employee's gender with
GET /genders/1
{
    "id": 1,
    "gender": FEMALE
}

Uma desvantagem é que /genders/1não é legível por humanos. Em vez disso /genders/female, você pode usar , mas nunca pode mudar o nome de um gênero sem quebrar os clientes. Essa é a chave sintética x troca de chave natural - flexibilidade versus legibilidade humana.

Você também pode considerar colocar todas as suas listas de valores em um terminal comum, como

/lists/genders/1
/lists/statuses/3

Isso esclarecerá aos clientes que todos são basicamente pares de valores-chave que pertencem a diferentes agrupamentos.

Eric Stein
fonte
0

Eu usaria algo entre 1 e 2, pelos motivos que David mencionou:

Você não deseja expor o ID das coisas, a menos que seja necessário.

No entanto, a exposição do ID pode se tornar necessária em algum momento. Se isso acontecer, a compatibilidade com versões anteriores é uma preocupação. Então, eu faria isso:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "name": "FEMALE"
    },
    "status": {
        "name": "FULL_TIME"
    }
}

Que possui as mesmas propriedades da opção 2; mas tem o benefício de que adicionar o ID posteriormente não introduz uma quebra de BC:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "id": 1,
        "name": "FEMALE"
    },
    "status": {
        "id": 3,
        "name": "FULL_TIME"
    }
}

Como Eric aponta nos comentários, isso ainda usa o nome da entidade como um identificador uniqe. Se o ID for introduzido posteriormente, o nome ainda deverá permanecer o mesmo, porque os clientes mais antigos poderiam (ou melhor, terão ) codificado para ele.

marstato
fonte
Essa abordagem introduz uma nova opção. Tendo 2 recursos diferentes: o primeiro para consulta e o segundo para criar ou atualizar. Embora possa parecer muito código, facilita a manutenção.
24517 Laiv
@ Laiv: eu não sugeri isso; por enquanto eu usaria o namepara consultar e atualizar.
marstato 24/02
1
Você deve observar que isso significa que um gênero ou status nunca pode ser renomeado. Se os clientes estiverem trabalhando apenas com o nome, esse nome será efetivamente o identificador exclusivo. Se os clientes precisam começar a usar um idcomo identificador exclusivo, isso é uma mudança radical.
Eric Stein