Qual é um padrão recomendado para o planejamento de terminais REST para alterações previstas

25

Tentar projetar uma API para aplicativos externos com previsão de mudança não é fácil, mas um pouco de reflexão inicial pode facilitar a vida mais tarde. Estou tentando estabelecer um esquema que dará suporte a alterações futuras, mantendo a compatibilidade com versões anteriores, deixando os manipuladores de versões anteriores em vigor.

A principal preocupação deste artigo é saber qual padrão deve ser seguido para todos os pontos de extremidade definidos para um determinado produto / empresa.

Esquema de Base

Dado um modelo de URL base do https://rest.product.com/eu idealizei que todos os serviços residem sob /apijuntamente com /authe outros parâmetros não baseados em descanso como /doc. Portanto, eu posso estabelecer os pontos de extremidade base da seguinte maneira:

https://rest.product.com/api/...
https://rest.product.com/auth/login
https://rest.product.com/auth/logout
https://rest.product.com/doc/...

Pontos de extremidade de serviço

Agora, para os próprios terminais. A preocupação com POST, GET, DELETEnão é o objetivo principal deste artigo e é a preocupação sobre os próprios atos.

Os pontos de extremidade podem ser divididos em espaços de nome e ações. Cada ação também deve se apresentar de forma a suportar mudanças fundamentais no tipo de retorno ou nos parâmetros necessários.

Tomando um serviço de bate-papo hipotético onde os usuários registrados podem enviar mensagens, podemos ter os seguintes pontos finais:

https://rest.product.com/api/messages/list/{user}
https://rest.product.com/api/messages/send

Agora, para adicionar suporte à versão para futuras alterações da API que podem estar com problemas. Poderíamos adicionar a assinatura da versão depois /api/ou depois /messages/. Dado o sendponto final, poderíamos ter o seguinte para a v1.

https://rest.product.com/api/v1/messages/send
https://rest.product.com/api/messages/v1/send

Portanto, minha primeira pergunta é: qual é o local recomendado para o identificador de versão?

Gerenciando o código do controlador

Portanto, agora que estabelecemos que precisamos oferecer suporte a versões anteriores, precisamos, de alguma forma, manipular o código para cada uma das novas versões que podem se tornar obsoletas ao longo do tempo. Supondo que estamos escrevendo pontos de extremidade em Java, poderíamos gerenciar isso por meio de pacotes.

package com.product.messages.v1;
public interface MessageController {
    void send();
    Message[] list();
}

Isso tem a vantagem de que todo o código foi separado por espaços de nome em que qualquer alteração de quebra significaria uma nova cópia dos pontos de extremidade do serviço. A desvantagem disso é que todo o código precisa ser copiado e correções de bugs desejadas para serem aplicadas a versões novas e anteriores precisam ser aplicadas / testadas para cada cópia.

Outra abordagem é criar manipuladores para cada nó de extremidade.

package com.product.messages;
public class MessageServiceImpl {
    public void send(String version) {
        getMessageSender(version).send();
    }
    // Assume we have a List of senders in order of newest to oldest.
    private MessageSender getMessageSender(String version) {
        for (MessageSender s : senders) {
            if (s.supportsVersion(version)) {
                return s;
            }
        }
    }
}

Isso agora isola o controle de versão para cada ponto de extremidade e torna as correções de bugs compatíveis com a porta traseira, na maioria dos casos precisando ser aplicadas apenas uma vez, mas significa que precisamos fazer um trabalho bem mais em cada ponto de extremidade individual para suportar isso.

Portanto, há minha segunda pergunta "Qual é a melhor maneira de projetar o código de serviço REST para suportar versões anteriores".

Brett Ryan
fonte

Respostas:

13

Portanto, há minha segunda pergunta "Qual é a melhor maneira de projetar o código de serviço REST para suportar versões anteriores".

Uma API ortogonal e cuidadosamente projetada provavelmente nunca precisará ser alterada de maneiras incompatíveis com versões anteriores; portanto, a melhor maneira é não ter versões futuras.

Obviamente, você provavelmente não conseguirá a primeira tentativa; Tão:

  • Faça a versão da sua API, exatamente como você está planejando (e ela é a versão, não os métodos individuais), e faça muito barulho a respeito. Verifique se seus parceiros sabem que a API pode mudar e se os aplicativos deles devem verificar se estão usando a versão mais recente. e aconselhe os usuários a atualizar quando um mais novo estiver disponível. O suporte a duas versões antigas é difícil, o suporte a cinco é insustentável.
  • Resista ao desejo de atualizar a versão da API a cada "release". Os novos recursos geralmente podem ser lançados na versão atual sem quebrar os clientes existentes; eles são novos recursos. A última coisa que você deseja é que os clientes ignorem o número da versão, pois, de qualquer maneira, é compatível com versões anteriores. Atualize a versão da API somente quando você absolutamente não puder avançar sem interromper a API existente.
  • Quando chegar a hora de criar uma nova versão, o primeiro cliente deve ser a implementação compatível com versões anteriores da versão anterior. A "API de manutenção" deve ser implementada na API atual. Dessa forma, você não está disposto a manter várias implementações completas; apenas a versão atual e vários "shells" para as versões antigas. A execução de um teste de regressão para a API agora obsoleta em relação ao cliente com estado de coma invertido é uma boa maneira de testar a nova API e a camada de compatibilidade.
SingleNegationElimination
fonte
3

A primeira opção de design de URI expressa melhor a ideia de que você está fazendo o versionamento de toda a API. O segundo poderia ser interpretado como versionamento das próprias mensagens. Então, isso é melhor IMO:

rest.product.com/api/v1/messages/send

Para a biblioteca do cliente, acho que o uso de duas implementações completas em pacotes Java separados é mais limpo, mais fácil de usar e mais fácil de manter.

Dito isto, existem métodos melhores para evoluir APIs do que versões. Concordo com você que você deve estar preparado para isso, mas penso no versionamento como o método de último recurso, para ser usado com cuidado e moderação. É um esforço relativamente grande para os clientes atualizarem. Há um tempo atrás, coloquei algumas dessas idéias em um post do blog:

http://theamiableapi.com/2011/10/18/api-design-best-practice-plan-for-evolution/

Para versões específicas da API REST, você encontrará esta postagem de Mark Nottingham útil:

http://www.mnot.net/blog/2011/10/25/web_api_versioning_smackdown

Ferenc Mihaly
fonte
3

Outra abordagem para lidar com o controle de versão da API é usar Version in HTTP Headers. Gostar

POST /messages/list/{user} HTTP/1.1
Host: http://rest.service.com
Content-Type: application/json
API-Version: 1.0      <----- like here
Cache-Control: no-cache

Você pode analisar o cabeçalho e manipulá-lo adequadamente no back-end.

Com essa abordagem, os clientes não precisam alterar a URL, apenas o cabeçalho. E isso também torna os pontos de extremidade REST sempre limpos.

Se algum cliente não enviou o cabeçalho da versão, você envia 400 - Solicitação incorreta ou pode lidar com isso com a versão compatível com versões anteriores da sua API.

sincerekamal
fonte