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 /api
juntamente com /auth
e 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
, DELETE
nã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 send
ponto 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".