Estratégia de versão de serviço de descanso
Estratégia de versão do serviço de descanso
Estou criando um novo serviço da Web e li alguns dos e-books da APIgee, nos quais o serviço de versão é recomendado. Eu entendo que existe alguma "batalha" entre manter as informações de versão no URL versus o cabeçalho. Pelo que li e compreendo, quero usar o controle de versão no cabeçalho.
Minha pergunta é; Como isso se parece na prática? Eu estou usando o Spring MVC 3.2. Você acabou de criar um método como este no mesmo controlador que responde a diferentes versões?
Ou isso está errado? Ou é mais comum criar pacotes diferentes que contenham versões diferentes do controlador? Ou existem outras maneiras?
O problema aqui é menos sobre onde a informação da versão reside (URI vs header) e mais sobre como você organiza o código para diferentes versões.
Eu duvido que exista uma única abordagem padrão. Depende apenas de quão diferentes são as versões.
Mudança de formato simples. Suponha, por exemplo, que a única diferença é que você mudou de XML em V1 para JSON em V2. Nesse caso, você pode usar exatamente o mesmo código, mas basta configurar o aplicativo para gerar JSON globalmente. Não há necessidade de pacotes ou controladores diferentes. (Por exemplo, você pode usar anotações JAXB para direcionar a saída JSON gerada por XML e Jackson.)
Modestas alterações no esquema. Digamos que a V2 introduza um pequeno número de alterações no esquema de quebra. Neste caso, provavelmente não faria sentido criar novos pacotes sobre ele. Você pode ter apenas lógica condicional simples em seu controlador para processar / servir a representação correta para a versão.
Principais alterações no esquema. Se as alterações do esquema forem profundas e abrangentes, talvez seja necessário mais do que controladores separados. Você pode até precisar de um modelo de domínio diferente (entidades / serviços). Neste caso, pode muito bem fazer sentido ter um conjunto paralelo de pacotes para controladores até as entidades, repos e talvez até tabelas de banco de dados.
Aplicando as idéias.
Abordagem 1. Aplicando essas ideias em seus exemplos do @RequestMapping, você poderia fazer o que você diz lá, mas se a resposta é exatamente a mesma entre as versões, então eles devem apenas delegar para um único método compartilhado:
Algo assim funcionaria. Se as ordens forem diferentes entre as versões, você poderá implementar as diferenças diretamente no método.
Estratégia de versão do serviço de descanso
Esta é uma boa e uma pergunta complicada. O tópico de design de URI é ao mesmo tempo a parte mais proeminente de uma API REST e, portanto, um compromisso potencialmente de longo prazo com os usuários dessa API.
Como a evolução de um aplicativo e, em menor escala, de sua API é um fato da vida e é até similar à evolução de um produto aparentemente complexo como uma linguagem de programação, o design de URI deve ter menos restrições naturais e deve ser preservado ao longo do tempo . Quanto maior o tempo de vida do aplicativo e da API, maior o comprometimento com os usuários do aplicativo e da API.
Por outro lado, outro fato da vida é que é difícil prever todos os recursos e seus aspectos que seriam consumidos por meio da API. Felizmente, não é necessário projetar a API inteira que será usada até o Apocalypse. É suficiente definir corretamente todos os endpoints de recursos e o esquema de endereçamento de cada recurso e instância de recurso.
Com o tempo, você pode precisar adicionar novos recursos e novos atributos a cada recurso específico, mas o método que os usuários da API seguem para acessar determinados recursos não deve ser alterado quando um esquema de endereçamento de recursos se tornar público e, portanto, final.
Este método se aplica à semântica do verbo HTTP (por exemplo, PUT deve sempre atualizar / substituir) e códigos de status HTTP que são suportados em versões anteriores da API (eles devem continuar a funcionar para que os clientes da API que trabalharam sem intervenção humana possam continuar trabalhando) Curtiu isso).
Além disso, como a incorporação da versão da API na URI interromperia o conceito de hipermídia como o mecanismo do estado do aplicativo (declarado na dissertação de doutorado de Roy T. Fieldings) por ter um endereço de recurso / URI que mudaria com o tempo, concluiria que a API As versões não devem ser mantidas em URIs de recursos por um longo tempo, o que significa que os URIs de recursos dos quais os usuários da API podem depender devem ser permalinks.
Claro, é possível incorporar a versão da API no URI de base, mas apenas para usos razoáveis e restritos, como a depuração de um cliente de API que funciona com a nova versão da API. Essas APIs com versão devem ser limitadas no tempo e estar disponíveis apenas para grupos limitados de usuários da API (como durante betas fechados). Caso contrário, você se compromete onde não deveria.
Algumas considerações sobre a manutenção de versões da API com data de expiração. Todas as plataformas / linguagens de programação comumente usadas para implementar serviços da Web (Java,.NET, PHP, Perl, Rails, etc.) permitem uma vinculação fácil do (s) ponto (s) de extremidade do serviço da Web a um URI de base. Dessa forma, é fácil reunir e manter uma coleção de arquivos / classes / métodos separados em diferentes versões da API.
A partir do POV dos usuários da API, também é mais fácil trabalhar e vincular-se a uma determinada versão da API quando isso é óbvio, mas apenas por tempo limitado, ou seja, durante o desenvolvimento.
A partir do POV do desenvolvedor da API, é mais fácil manter diferentes versões da API em paralelo usando sistemas de controle de origem que trabalham predominantemente em arquivos como a menor unidade de versão (código-fonte).
No entanto, com as versões da API claramente visíveis no URI, há uma ressalva: também é possível objetar essa abordagem, já que o histórico da API se torna visível / aparente no design da URI e, portanto, está sujeito a mudanças ao longo do tempo que vai contra as diretrizes do REST. Concordo!
A maneira de contornar essa objeção razoável é implementar a versão mais recente da API sob URI de base da API sem versão. Nesse caso, os desenvolvedores de clientes da API podem optar por:
desenvolver contra o mais recente (comprometendo-se a manter o aplicativo protegendo-o de eventuais alterações de API que possam quebrar seu cliente de API mal projetado).
ligar a uma versão específica da API (que se torna aparente), mas apenas por um tempo limitado.
Por exemplo, se a API v3.0 for a versão mais recente da API, as duas seguintes devem ser aliases (ou seja, comportar-se de forma idêntica a todas as solicitações da API):
Além disso, os clientes da API que ainda tentam apontar para a API antiga devem ser informados para usar a versão da API anterior mais recente, se a versão da API que eles estiverem usando for obsoleta ou não for mais suportada. Então, acessando qualquer um dos URIs obsoletos como estes:
deve retornar qualquer um dos códigos de status HTTP 30x que indicam o redirecionamento usado em conjunto com o cabeçalho HTTP de local que redireciona para a versão apropriada do URI do recurso que permanece como esta:
Existem pelo menos dois códigos de status HTTP de redirecionamento que são apropriados para cenários de versão de API:
301 Movido permanentemente, indicando que o recurso com um URI solicitado é movido permanentemente para outro URI (que deve ser um permalink de instância de recurso que não contém informações sobre a versão da API). Esse código de status pode ser usado para indicar uma versão de API obsoleta / sem suporte, informando ao cliente de API que um URI de recurso com versão foi substituído por um link permanente de recurso.
Seu versionamento de API está errado, e é por isso que decidi fazer isso de três formas diferentes e erradas.
No final, decidi que a maneira mais justa e equilibrada era irritar todo mundo igualmente. É claro que estou falando sobre versionamento de APIs e não desde as ótimas guias # x201C versus espaços & # x201D; debate tenho visto tantas crenças fortes em campos totalmente diferentes.
Isso foi ótimo. Quando eu construí Eu fui pwned? (HIBP) no final de novembro, foi concebido para ser um serviço simples e rápido que algumas pessoas usariam. Eu acho que é justo dizer que os dois primeiros pontos foram alcançados, mas não o último. Não era um "número um", na verdade, no final da primeira semana, era mais do que o Google Analytics poderia suportar. O ponto é que você não pode sempre prever o futuro quando você escreve sua API e em algum momento você pode precisar mudar algo que as pessoas já dependem.
Mas aqui está o problema & # x2013; toda vez que você começa a falar sobre qualquer coisa relacionada a APIs via HTTP, isso acontece:
Todo caminho que você vira, há diferentes filosoficos sobre o caminho certo & # x201D; e muito para trás e para frente no REST, o que é RESTful, o que não é e se é importante. Vamos falar sobre as alterações da API, o impacto sobre as versões, por que há tantas idéias divergentes sobre como isso deve ser feito e, por fim, por que nenhuma das brincadeiras é tão importante quanto realmente fazer as coisas.
Puxando mais dados de violação.
Tendo em mente que a mesma API é usada para o recurso de pesquisa no site e agora também por terceiros criando tudo, de aplicativos de smartphone a ferramentas de teste de penetração, a resposta acima funcionou bem no início, mas foi limitada. Por exemplo, esta resposta não funciona tão bem:
Por quê? Porque & # x201C; BattlefieldHeroes & # x201D; é o Pascal-cased que é ótimo para combinar com classes CSS codificadas (embora provavelmente não seja uma boa abordagem a longo prazo) e por ter um & # x201C; stable & # x201D; nome para se referir a (eu não vou alterá-lo, mesmo que haja uma segunda violação), mas não é adequado para exibição como um título. Tudo isso sai do Armazenamento de Tabelas do Azure e eu entro no SQL Azure para extrair dados relacionais que realmente descrevem a violação. Um dos atributos nesse armazenamento relacional é o nome que você vê acima.
O que eu realmente queria fazer era algo mais assim:
Pegue? Para o ponto anterior sobre o nome da violação, que ainda está lá no atributo name, mas agora temos um título também. Isto é o que você mostra para as pessoas & # x2013; & # x201C; Battlefield Heroes & # x201D; & # x2013; mas mais importante, se o Gawker for penhorado novamente, posso nomear a violação de algo como Gawker2014 e o título pode ser algo amigável ao longo das linhas de Gawker (Ataque Eletrônico do Exército Sírio) & # x201D ;. Ele segmenta o que é estável e previsível daquilo que não é e significa que as pessoas podem criar dependências, como imagens ou outros recursos, no atributo name.
Os outros dados devem ser bem claros: a data da violação, quando foi adicionada ao sistema, o número de contas pwned, a descrição da violação (novamente, isso pode mudar se o palavreado precisar ser ajustado) e & # x201C; DataClasses & # x201D ;. Uma das coisas que muitas pessoas estavam pedindo era uma descrição do que estava comprometido na brecha, então agora há um monte de atributos que podem ser adicionados através de uma coleção sobre a violação em si. Eu já estou mostrando isso abaixo de cada violação na página dos sites da Pwned (essa é outra razão pela qual eu posso agora ajustar algumas das descrições).
Esta é uma mudança urgente. Enquanto o sentimento da API é o mesmo & # x2013; forneça um nome de conta, receba de volta uma lista de violações & # x2013; não há mais uma matriz de strings de nomes de violações. Se eu simplesmente substituísse a API antiga por essa, as coisas iriam quebrar. APIs. Devo. Evoluir.
O software evolui, as APIs devem ser versionadas.
Vamos ser sinceros sobre isso: o mundo segue em frente. A API para o HIBP durou cerca de 2 meses, não porque foi mal projetada, mas porque o serviço se tornou descontrolado e inesperadamente bem-sucedido. Eu gosto desse tipo de problema, e você também deveria.
Agora eu tive uma escolha; ou eu poderia me contentar com o que eu tinha e privar as pessoas de uma maneira melhor, eu poderia adicionar ao serviço existente de uma forma não violenta ou eu poderia criar uma nova versão (embora expondo a mesma entidade de uma maneira diferente) e construir É a melhor maneira de saber como; sem bytes desnecessários, modelados corretamente (até que eu decida que uma nova versão é mais correta) e uma boa representação da entidade que eu estou tentando finalmente entrar nos consumidores & # x2019; apps.
Não há nada de errado em introduzir uma nova versão de uma API quando é a coisa mais sensata a ser feita. Por todos os meios, faça o seu melhor para obtê-lo & # x201C; direita & # x201D; desde o primeiro dia, mas fazê-lo com a expectativa de que "certo" & # x201D; é um estado temporário. É por isso que precisamos estar aptos para a versão.
Os vários campos de versionamento.
Certo, então quão difícil pode ser esse negócio de versionamento? Quero dizer, deve ser um exercício simples, certo? O problema é que isso é muito filosófico, mas em vez de ficar atolado nisso por enquanto, deixe-me delinear as três escolas comuns de pensamento em termos de como elas são praticamente implementadas:
URL: basta digitar a versão da API no URL, por exemplo: haveibeenpwned / api / v2 / breachedaccount / foo Cabeçalho de solicitação personalizada: você usa o mesmo URL de antes, mas adiciona um cabeçalho como & # x201C; api-version: 2 & # x201D; Aceitar cabeçalho: você modifica o cabeçalho de aceitação para especificar a versão, por exemplo, & # x201C; Accept: application / vnd. haveibeenpwned. v2 + json & # x201D;
Tem havido muitas, muitas coisas escritas sobre isso e eu vou linkar para eles no final do post, mas aqui está a versão abreviada:
Os URLs são uma droga porque devem representar a entidade: na verdade, eu concordo com isso na medida em que a entidade que estou recuperando é uma conta violada, não uma versão da conta violada. Semanticamente, não é realmente correto, mas é fácil de usar! Cabeçalhos de pedidos personalizados são uma droga porque não é realmente uma forma semântica de descrever o recurso: A especificação HTTP nos dá um meio de solicitar a natureza que gostaríamos do recurso representado por meio do cabeçalho de aceitação, por que reproduzir? esta? Aceitar cabeçalhos chupados porque são mais difíceis de testar: não posso mais apenas fornecer a alguém um URL e dizer: "##201C; aqui, clique em" & # x201D ;, em vez disso, eles devem construir cuidadosamente a solicitação e configurar o cabeçalho de aceitação adequadamente .
Os vários argumentos a favor e contra cada abordagem tendem a ir de & # x201C; este é o & # x2018; certo & # x2019; maneira de fazer isso, mas é menos prático & # x201D; através de & # x201C; Esta é a maneira mais fácil de criar algo consumível que, portanto, faz com que seja "# & # x2018; right & # x2019; & # x201D ;. Há muita discussão sobre hipermídia, negociação de conteúdo, o que é & nbsp; REST & # x201D; e todo tipo de outras questões. Infelizmente, isso muitas vezes é filosófico e perde a visão de qual deve ser o objetivo real: construir um software que funcione e particularmente para uma API, tornando-a facilmente consumível.
É sobre ter um contrato estável, estúpido!
Mais importante do que todas as reclamações e delírios sobre como fazer isso dessa maneira ou daquela maneira é dar estabilidade às pessoas. Se eles investem seu esforço suado escrevendo código para consumir sua API, então é melhor que você não a interrompa mais adiante.
Honestamente, os debates sobre o que é o & # X201C; RESTful & # x201D; contra o que não é como se o próprio termo ditasse seu sucesso é apenas louco. Transforme essa discussão em "Aqui estão as razões práticas pelas quais isso faz sentido, e é isso que pode acontecer se você não o fizer", e eu farei de tudo. O problema é que até mesmo as vozes da razão dentro das discussões barulhentas deixam dúvidas quanto ao que realmente é a melhor abordagem e, portanto, eu alcancei um compromisso & # x2026;
Aqui estão 3 maneiras erradas de consumir a API de HIBP que você pode escolher agora.
Ok, agora que estamos claramente estabelecidos, mas você está errado, eu gostaria de dar a você a escolha de escolher qualquer uma das três formas erradas. Espere & # x2013; o que?! É assim: no entanto, eu implemento a API, ela será muito difícil de consumir, muito acadêmica, muito provavelmente falha no proxy ou algo do tipo. Em vez de escolher um caminho errado, decidi dar-lhe todas as 3 formas erradas e pode escolher aquele que é o menos errado para você.
Caminho errado 2 - cabeçalho de solicitação personalizada:
Inevitavelmente, alguém vai me dizer que fornecer 3 formas erradas é a coisa errada a fazer. Não significaria mais código kludge para manter? Não, isso significa simplesmente que a implementação da API da Web subjacente é decorada com dois atributos:
O primeiro é simplesmente uma restrição de roteamento que implementa o RouteFactoryAttribute. Eu passo na rota e passo a versão que pode mapear para aquela rota, então a implementação procura a presença de um & # x201C; api-version & # x201D; cabeçalho ou um cabeçalho de aceitação correspondente a esse padrão:
Se a versão especificada em uma dessas combina com a especificada na restrição de roteamento, então é o método que será invocado. Esta é uma adaptação simples desta amostra no CodePlex.
O segundo atributo que decora o método GetV2 acima é cortesia da Web API 2 e do recurso de roteamento de atributos. É claro que sempre poderíamos fazer roteamento na API da Web, mas isso geralmente era definido globalmente. O roteamento de atributos como esse traz a definição de rota para o contexto em que ela é aplicada e facilita a visualização da ação do controlador que será chamada por qual rota. Isso também significa que as implementações de todas as três formas erradas de chamar a API estão reunidas em um único local.
Então, em suma, não, isso não cria um monte de kludge e é muito fácil de manter. Cada uma das três abordagens retornará exatamente o mesmo resultado e, o mais importante, elas permanecerão estáveis e não serão alteradas de nenhuma forma e, no final das contas, será a mais importante. importante, independentemente de qual opção você escolher. Toda a implementação agora também está claramente documentada na página da API do site.
Mas e se você não especificar uma versão?
Você sabe o pouco onde eu disse que você não pode quebrar o que já está lá fora? Sim, isso significa que se você fizer o que faz agora, # x2013; não especifique uma versão & # x2013; então você começa o que você recebe agora. Em outras palavras, nenhum pedido para uma versão específica significa que você obtém a versão 1.
Eu estou bem com isso, independentemente de ter atingido este ponto por padrão. Eu sei que algumas pessoas sempre gostam de retornar a versão mais recente se um número não for especificado, mas IMHO que quebra todo o contrato estável & # x201D; # x201D; objetivo; o que você obtém da API hoje pode ser completamente diferente do que você recebe amanhã se eu revisá-lo. Isso seria uma droga e quebraria as coisas.
Você tem 3 opções, mas minha preferência pessoal é & # x2026;
Eu tenho o luxo de controlar tanto a API quanto o consumidor primário do site da HIBP. Dado que eu forneci 3 opções para consumir a API, qual delas eu mesmo uso?
Eu fui com o favorito filosófico que é especificá-lo através do cabeçalho de aceitação. Eu não acho que isso é certo e os outros estão errados, ao contrário, eu acho que isso faz mais sentido por duas razões principais:
Concordo que o URL não deve mudar: se concordarmos que o URL representa o recurso, a menos que estejamos tentando representar versões diferentes do próprio recurso, não, não acredito que o URL deva mudar. As brechas para foo são sempre as brechas para foo e eu não acho que só porque eu mudo os dados retornados para foo que a localização de foo deve mudar. Concordo que os cabeçalhos de aceitação descrevem como você deseja os dados: Esta é uma semântica da especificação de HTTP e assim como a semântica dos verbos de solicitação faz muito sentido (isto é, estamos obtendo, colocando ou excluindo ou postando), O mesmo acontece com a maneira como o cliente gostaria que o conteúdo fosse representado.
De maneira nenhuma isso significa que eu acho que os outros dois estão errados e, francamente, não há melhor maneira de compartilhar a API com alguém do que dizer: "Aqui, clique aqui, mas quando eu puder facilmente construir o pedido e gerenciar os cabeçalhos, eu fui com esta rota.
Na verdade, pensando nisso, eu também uso a versão na rota do domínio. Por quê? Apenas através do processo de escrever esta API eu estava constantemente me comunicando com as pessoas sobre as formas de consultá-las (mais sobre isso mais tarde) e os atributos que ela retorna. Ser capaz de passar por um e-mail e dizer "Ei, aqui está o que eu estou pensando" ##201D; e eles simplesmente clicam e obtêm resultados é inestimável. Esse é o ponto que os proponentes da abordagem de versionamento de URLs fazem com toda a razão: você simplesmente não pode fazer isso quando estiver dependente de cabeçalhos.
Ah, e no caso de você estar me checando, no momento em que escrevo, eu ainda não rolei o site para a v2 da API. Agora que os dados de violação são extraídos na API quando ocorre uma pesquisa, isso significa que eu tenho o luxo de não carregar todas as violações na origem na carga inicial (isso nunca será sustentável à medida que o conjunto de dados se expande). Isso salvará um monte de tráfego de saída e acelerará as coisas para as pessoas em termos de obter o site carregado, mas isso também significa um pouco mais de trabalho do meu jeito. Fique ligado.
No fechamento.
Claramente eu tenho sido um pouco irônico aqui no que diz respeito a tudo estar errado, mas honestamente, quanto mais você lê sobre isso e quanto mais perguntas você faz, mais errado todo caminho parece de uma maneira ou de outra. Na verdade, eu sei muito bem que existem aspectos da minha implementação que serão referidos como "errados" e "x201D". (Eu posso pensar em pelo menos um par) e, naturalmente, eu estou me preparando para o potencial ataque de feedback para esse efeito. A coisa é, porém, cada uma dessas opções funciona e, francamente, para todos os efeitos práticos, eles funcionam tão bem quanto os outros.
Se eu puder deixar outras pessoas pensando em como atualizar suas APIs com um pensamento final: ninguém usará sua API até que você a tenha criado. Pare de procrastinar. Nenhuma dessas opções é "ruim" e "x201D"; em qualquer sentido tangível, eles são apenas diferentes. Eles são todos facilmente consumíveis, todos eles retornam o mesmo resultado e nenhum deles é susceptível de ter qualquer impacto real sobre o sucesso do seu projeto.
Referências.
Stack Overflow: Práticas recomendadas para o versionamento de API? (ótima pergunta, ótimas respostas, fechada como "não construtiva", eu assumo porque "Bill the Lizard & # x201D; saiu do lado errado da cama naquela manhã) Blog do Lexical Scope: How are REST APIs com versão? (boa comparação de práticas de controle de versão entre serviços, ainda que alguns anos atrás) CodePlex: Exemplo de restrição de roteamento (vinculado na página da API da Web da Microsoft como um exemplo de APIs de controle de versão adicionando um cabeçalho personalizado) CodeBetter: Versionamento RESTful Serviços (muito pragmáticos e uma boa descrição das várias maneiras pelas quais uma API pode mudar) Blog de Vinay Sahni: Práticas recomendadas para projetar uma API RESTful pragmática (ele está argumentando sobre o versionamento de URL por causa de & # x201C ; explorabilidade do navegador & # x201D;) Lans Pivotal: versionamento da API (boa visão das opiniões conflitantes existentes) Pilha Web de Amor: ASP. NET Web API Versioning com Tipos de Mídia (bom passo-a-passo de criar um aplicativo para suportar versionamento por negociação de conteúdo)
Oi, sou Troy Hunt, escrevo este blog, crio cursos para a Pluralsight e sou diretor regional da Microsoft e MVP que viaja pelo mundo falando em eventos e treinando profissionais de tecnologia.
Oi, sou Troy Hunt, escrevo este blog, crio cursos para a Pluralsight e sou diretor regional da Microsoft e MVP que viaja pelo mundo falando em eventos e treinando profissionais de tecnologia.
Próximos eventos.
Eu normalmente faço workshops particulares em torno destes, eis os próximos eventos públicos em que estarei:
Insights de versão de API RESTFul.
Quando se trata de versionamento de APIs, existem muitas práticas recomendadas e insights, mas ainda não há uma prática recomendada sólida.
Para entender o versionamento da Restful API, primeiro precisamos entender o problema.
O problema de versionamento.
Ninguém deve quebrar seus clientes.
Mudar sua API é uma coisa bem fundamental de se fazer. Você está literalmente mudando a interface com a qual os clientes estão se comunicando antes da sua mudança. O ponto é que você não quer quebrar os clientes que se conectam a você hoje, embora ainda precisem alterar (adicionar, remover ou alterar) sua API. Isto não afeta apenas a aparência da API, ou seja, os formatos de solicitação ou resposta também podem incluir funcionalidade, por exemplo. os padrões agora funcionam de maneira diferente.
Infelizmente, não importa o quão brilhante você arquitetar sua solução, você pode achar que, ao longo do tempo, você tem que alterá-lo.
Pode-se argumentar que os micro-serviços contornam esse problema por serem pequenos. Micro-serviços devem ser tão pequenos que são tão simples que você não terá espaço para mudá-los - você pode ler mais sobre o que são micro-serviços, mas não vou falar sobre eles hoje.
Então, supondo que você tenha que alterar sua API, o que você realmente quer fazer é garantir que seus clientes saibam que sua API mudou e dar a eles alguma forma de decidir programaticamente, ou de outra forma, para qual versão eles se conectarão. que eles continuem trabalhando.
Agora que entendemos o problema, quais são as soluções possíveis?
As 4 possíveis soluções para versionamento de API.
Antes de dizer mais, gostaria de dizer que cada opção tem seus prós e contras. Qual deles você acaba escolhendo pode ser afetado não apenas pelas melhores práticas, restrições da infraestrutura e outros aspectos. Atualmente, não há a melhor solução para o Restful API Versioning, e até mesmo aqueles que acreditam que não devemos precisar de versões de nossas APIs, como esta postagem "Don 't Version Your Web API" de Jan Stenberg.
Nesse método, a versão é colocada explicitamente no URI da API. Por exemplo:
. / maps / version / 2. / maps / version / 2 / buildings / version / 3. / maps / v2 / buildings.
As opções acima mostram três maneiras diferentes de expor as versões por meio do seu URI.
Em primeiro lugar, um modelo de hierarquia grosseira de granulação grossa e, em seguida, um método de subtópicos mais refinado, que nos dá a capacidade de desenvolver elementos separados da API (estradas, neste caso). A terceira opção mostra apenas o modelo um pouco menos expressivo de ter um único argumento de versão (por exemplo, 'v2') sem o nó explícito de 'versão' na hierarquia de URL.
Eu, pessoalmente, não gosto desse modelo. Do ponto de vista mais puro, argumenta-se que o URI em REST deve representar apenas a estrutura de recursos.
Uma versão não é um recurso, é um atributo do recurso.
No entanto, em um lado positivo, posso ver como é muito claro o que está acontecendo! Também vejo isso sendo recomendado por muitos fornecedores de ferramentas de API. Aqui estão mais alguns prós e contras para este método específico.
Já podemos ver como as vantagens para uma pessoa podem ser vistas como desvantagens para outra.
URIs sugam porque devem representar a entidade - eu quero recuperar a entidade de mapas, não uma versão da conta violada. Semanticamente, não é realmente correto, mas é muito fácil de usar!
2) O cabeçalho Accept.
Há um cabeçalho HTTP conhecido, chamado Accept, que é enviado em uma solicitação de um cliente para um servidor. Por exemplo.
Esta notação está dizendo que eu, o cliente, gostaria que a resposta estivesse no json por favor.
Os cabeçalhos de aceitação estão usando esse cabeçalho para criar seus próprios tipos de recursos, por exemplo:
Aceite: application / vnd. myapi. v2 + json.
Agora; espere aqui porque essa é uma sintaxe um pouco estranha que nos encontramos olhando, então vamos analisá-la.
As especificações da Internet dizem que eu, como fornecedor, posso definir (e registrar) um tipo de mídia. Se eu fizer isso, é chamado (sem surpresa) um tipo de mídia 'fornecedor'. Eu deveria prefixar meu tipo com 'vnd' para deixar claro que é minha própria definição. Então preciso dizer qual é o nome do tipo real que estou definindo, e. 'myapi'. No entanto, a especificação não informa nada sobre um número de versão, então as pessoas começaram a dizer que o nome do tipo de mídia inclui um número de versão, por exemplo:
Agora, como eu, como aplicativo cliente, ainda preciso definir qual tipo de conteúdo eu realmente quero (além da versão), isso pode ser adicionado como um sufixo ao tipo de mídia solicitado, por exemplo. '+ json' neste caso.
Observe que há um método alternativo de fazer isso sem qualquer pré-registro do tipo de mídia usando x. como o prefixo:
O uso do cabeçalho Accept parece um pequeno truque da especificação para mim - mas funciona e é bem conhecido, se não for realmente especificado como tal.
O maior problema com esse método é que ele é bastante oculto - e as coisas ocultas são sempre um pouco mais difíceis de se trabalhar. É quase sempre melhor ser explícito no que você está fazendo. Também suspeito que pedir ao seu administrador de firewall para abrir o firewall deles para qualquer tipo de mídia antigo pode levar a um grande risco de segurança.
No entanto, parece muito mais fácil passar o cabeçalho Accept: vnd do que passar cabeçalhos de solicitação customizados.
Aceitar cabeçalhos, porque são mais difíceis de testar - não posso mais apenas fornecer a alguém um URL e dizer "aqui, siga este link", em vez disso, eles precisam construir cuidadosamente a solicitação e configurar o cabeçalho de aceitação adequadamente.
3) cabeçalho de pedido personalizado.
Nas especificações HTTP originais, você pode definir qualquer cabeçalho HTTP antigo que você gostou na solicitação do cliente, desde que você tenha pré-fixado com "X", por exemplo
As especificações mudaram e você ainda pode passar qualquer cabeçalho de pedido antigo, mas ele não precisa mais ser pré-fixado com "X".
Isso significa que você pode pedir aos clientes da sua API para passarem algo assim.
Esta opção não é recomendada nem usada muito devido aos seguintes motivos:
Alguns dos roteadores (embora hoje as coisas sejam diferentes e a maioria deles transmitirá todos os cabeçalhos) podem rejeitar toda a solicitação http ou simplesmente não passar o cabeçalho especial. Depurar isso pode ser uma coisa muito difícil de fazer. Eu tive a mudança para encontrar uma coisa dessas, onde algumas máquinas estavam se conectando através de diferentes roteadores e um roteador em particular tinha sido configurado para remover tipos de cabeçalho não padrão. Permitiu que a mensagem passasse - incluindo todos os outros cabeçalhos - mas apenas silenciosamente removeu os que não gostou!
Está escondido novamente - assim como o cabeçalho Accept. O cabeçalho Accept já é uma maneira de ser bem explícito sobre o que o cliente aceita, então se vamos esconder coisas, pelo menos vamos usar um jeito que é conhecido.
Cabeçalhos de solicitações personalizadas são uma droga, porque não é uma maneira semântica de descrever o recurso. A especificação HTTP nos dá um meio de solicitar a natureza que gostaríamos que o recurso fosse representado por meio do cabeçalho de aceitação. Por que reproduzir isso?
4) O Parâmetro URI.
Aqui está uma óbvia que eu não vejo muitas pessoas usando:
Este método é muito usado pela Amazon, Google e Netflix, mas ainda não é tão popular. Hmmm - talvez haja algo para isso ?!
Versões de pensamento desde o primeiro dia.
Com isso quero dizer - quando você cria sua API atual, acredita que ela mudará, mesmo que não seja hoje - algum dia. Isso afetará o que você codifica - não apenas suas decisões sobre onde colocar o número da versão.
Se você é um desenvolvedor ágil, então pode estar gritando na tela agora mesmo.
Ágil é tudo sobre hoje. Na verdade, diz especificamente - não codifique para coisas que não estão na especificação. No entanto, vamos ser claros: codificar para extensibilidade não é o mesmo que colocar as extensões em si - que é sobre o que a metodologia ágil fala.
Deixando espaço para futuras versões se encaixa muito bem ágil.
Versão de uma API é difícil!
Sim, é, no entanto, realmente não é mais difícil do que manter o código do cliente e do servidor em sincronia. You should remember that Building Restful APIs is much easier that SOAP web services and is supported pretty much by all.
Try to allow for extensibility in your code from day one.
With regard to how to specify the version - If you do have a versioning convention in mind then make sure that your infrastructure can handle it.
Read more posts by this author.
Compartilhe este post.
Subscribe to REST API and Beyond.
Get the latest posts delivered right to your inbox.
or subscribe via RSS with Feedly!
RESTful API Basic Guidelines.
Your data model has started to stabilize and you're in a position to create a public API for your…
REST API vs SOAP Web Services Management.
Back in the day Web Services were the de facto standard for accessing "systems of record". SOAP web services…
Versioning RESTful Services.
I’ve talked about this in various venues and also cover it in my Pluralsight REST Fundamentals course, but the topic of how to version RESTful services has been popping up a bunch recently on some of the ASP. NET Web API discussion lists, and my friend Daniel Roth asked if I could serialize some of that presentation content into a blog post – so here goes.
First, note that while the focus here is on RESTful services and not just HTTP services, the same principles can potentially apply to HTTP services that are not fully RESTful (for example, HTTP services that do not use hypermedia as a state transition mechanism).
When talking about versioning, the most important question to ask is “what are you wanting to version?” The logical extension to this question is, “what is the contract between your service and client?” This is naturally important since the contract is the thing you want to version.
In the “old world” of Web services, the contract was the service. Service actions (and associated semantics) along with data formats and other metadata were covered by the definition of the service, which was exposed as a single URL (the service, that is – I’m grouping together all RMM L0 services here). As such, when it came to the question of how to version the service, the answer was generally pretty simple: if the contract is the service, and the service is exposed as a URL, then the solution is to version the URL. As such, you’ll see a lot of this if you browse around –
(Not trying to pick on NuGet here – it just happens to be a service API that I’m pretty familiar with at the moment)
It doesn’t take much imagination to see how unwieldy this can get after even a few iterations – especially when you’re clients are interacting with the service by generating strongly typed proxies and then pretending that there is no network (yes, I am picking on WCF here).
So how is this different for RESTful services?
Well, we should start by again asking the question, “What is the contract for a RESTful service?” The answer is, IMHO, the uniform interface. The uniform interface is made up of 4 constraints –
Identification of resources Manipulation through representations Self-descriptive messages Hypermedia as the engine of application state (HATEOAS)
While all of these constraints are important to understand and take into consideration for the overall design of a RESTful system, I’m going to highlight the first 2 with respect to versioning.
First, let’s look at resources. A resource is any named information or concept – this is nice from a philosophical perspective, but maybe a little less helpful in how it enables us to think about versioning, so let’s look at it another way (as Fielding describes). A resource is a 0..n mapping between an identifier and a set of entities that changes over time. Here’s a concrete example:
I have 3 daughters: Grace, Sarah, and Abigail. However, this was not always the case – in fact, during the time period before Grace was born, the resource map of my family looked like the following:
As you can see, I had defined (in my mind) a bunch of named concepts – and at that point in time, they didn’t map to any actual entities (e. g. kids). Now, when Grace was born, the resource map looked like the following:
As you can see here, all of my named concepts map to a single entity – Grace. But what about when Sarah came along? Then the map changed to the following:
As you can now see, my “children” collection resource maps to multiple entities, and “youngest child” now maps to Sarah rather than Grace.
The point here is that the resource *concept* has not changed here – and more importantly, though the specific entity mappings have changed over time, the service has done this in a way that preserves the meaning of identified domain abstraction (e. g. children).
A representation, on the other hand, is an opaque string of bytes that is effectively a manifestation of a resource. Representations can come in many different formats and the process of selecting the best format for a given client-server interaction is called content negotiation. The self-descriptive message constraint of the uniform interface adds that the information needed to process a representation, regardless of format, is passed in the message itself.
I wanted to give this brief explanation of resources and representations because it’s important to have a clear understanding of what they are so that you can know when to version them. So let’s get back to versioning…
So, if the contract for a RESTful service is the uniform interface, then the answer to the question of how to version the service is “it depends on which constraint of the uniform interface you’re changing.” In my experience, there are 3 common ways that you can version (I’m sure there are more, but these are the 3 that I’ve come across most regularly).
Versioning Strategy 1: Adding content to a representation.
In the case where you’re adding something to a representation – let’s say that you’re adding a new data field “SpendingLimit” to a customer state block as follows:
“Name”: “Some Customer”,
In this case, the answer to the versioning question is to just add it. Now, this assumes that your clients will ignore what they don’t understand. If you’ve written clients in such a way that you can’t make that assumption, then you should fix your clients J – or perhaps you need to look at the next strategy…
Versioning Strategy 2: Breaking changes in a representation.
In the case where you’re either removing or renaming content from an existing representation design, you will be breaking clients. This is because even if they are built to ignore what they don’t understand, by making this sort of change on the server, you’re changing what they already understand. In this case, you want to look at versioning your representation . HTTP provides a great facility for doing this using content negotiation. For example, consider the following:
GET localhost:8800/bugs HTTP/1.1.
This request gives me the following response fragment – as you can see, I’m working from an HTML base media type:
Now what if, for some reason, I needed to change the link relationship values? Remember that based on the hypermedia constraint of the uniform interface, my client needs to understand (e. g. have logic written against) those link relationship values, so renaming them would break existing clients. However, in this case, I’m not really changing the meaning of the resources or the entities that the resources map to. Therefore, I can version my representation and enable clients that know how to work with the newer version to request the newer version using the HTTP accept header as follows:
Therefore, this request:
GET localhost:8800/bugs HTTP/1.1.
Will now give me the new response format:
One other thing that I want to mention here – you’ve probably noticed that I’m using the language of representation design and representation versioning as opposed to content type design/versioning. This is deliberate in that many (most?) times, you’re going to design your representations completely on top of existing content types (e. g. xml/json/html/hal/etc). Without wading into the custom media type debate in this post, my point is that when I’m talking about versioning the representation here, I’m talking about versioning the domain-specific aspects of your representation that your client needs to be aware of.
Versioning a representation over an existing media type will look slightly differently from what’s shown above in that you’ll pass a standard media type identifier in the HTTP accept header along with an additional metadata element to identify your representation-specific aspects and then do content negotiation based on the combined description. There are several different ways to add the representation-specific metadata, including media type parameters and custom HTTP headers.
Versioning Strategy 3: Breaking the semantic map.
In both of the prior strategies, all changes, breaking and non-breaking, have been related to the representations. This is a really good thing as it enables the resources (and more importantly, the URL resource identifiers) to remain stable over time. However, there may be occasions – hopefully rarely – when you need to break the meaning of a resource and therefore the mapping between the resource and its set of entities. As an example, as I get older and my kids grow up and leave, let’s say that I start returning my pets as children. In this case, I’ve changed the meaning of the “children” resource and thereby broken that aspect of the contract between my client and service. The solution then, in this case, is to version the resource identifier itself.
If I did a lot of this sort of resource versioning, it is very possible that I could end up with an ugly-looking URL space. But REST was never about pretty URLs and the whole point of the hypermedia constraint is that clients should not need to know how to construct those URLs in the first place – so it really doesn’t matter whether they’re pretty or ugly – to your client, they’re just strings.
So to summarize.
This post ended up being longer than I had planned, so here’s the summary.
In REST, the contract between clients and services is the uniform interface How you version depends on what part of the uniform interface you’re changing If you’re adding only, go ahead and just add it to the representation. Your clients should ignore what they don’t understand If you’re making a breaking change to the representation, version the representation and use content negotiation to serve the right representation version to clients If you’re changing the meaning of the resource by changing the types of entities it maps to, version the resource identifier (e. g. URL)
About Howard Dierking.
I also think that version in the URL is a part of base URL from client’s point of view, so basically changing version does not change semantics for the resources, but only response (representation). When you request GET /api/v1/books or GET /api/v2/books it’s like requesting GET /api/books with client designed for using with v1 and v2 respectively. You don’t hardcode version in client, you configure it from the outside and use relative resources (for example /books ) so the only problem here would be misconfiguration (e. g. using v1 client with v2 resources).
Sorry for resurrecting such old thread, but I must ask: did you write down that new post ;-)?
In my opinion in your second approach you are confusing representation with entity structure. Representation is about the format in which you send the information or your client expects to receive it (xml, html, json, etc), in this case the resource that you are pointing to. Which means if I request the same resource but with different representation (xml, json, etc) at the same instant of time then my resources should point to the same exact entity structure and contents but with a different format, the thing that changes is the way it is dressed.
If you add a new field to the entity your resource is mapping to (like in the first approach you describe: adding SpendingLimit to customer entity), what you are really doing is creating a new version of your entity with its own set of properties (in this case with all the properties from the previous plus a new one), then you need to create a new resource (or redefine the mappings) to map to that entity as you do in your children example when having a new child.
This is similar to what Roy Fielding describes in his blog when talking about bulk operations: …If you find yourself in need of a batch operation, then most likely you just haven’t defined enough resources… (roy. gbiv/untangled/2008/rest-apis-must-be-hypertext-driven#comment-743). In the sense that changes to the information structure (in this case number of entities mapped by the resource) is about identifying those entities by mapping them to a proper resource.
Based in all that, in my opinion and experience versioning a REST API in terms of changing entity structure should be done in the URI, but at the level of resource id rather than at the level of API, which means later and not earlier in the URL.
Doing it this way allows the client to request specifically the resource they want which maps to the entity they need. Without having to mess with headers and custom media types which is really problematic when implementing in a production environment.
‘especially when you’re clients’ should probably read ‘especially when your clients’
Must take issue with the “before Grace was born, the resource map of my family looked like the following” showing a Grace node (not subscribing to “a boy named Sue” or “the grace of God” unconditionally), e. g. AFTER birth of Sarah the Sarah node appears.
haha, no worries. I like beer a great deal, but I live in WI. So…. there’s that.
clearly, I’ve done a terrible job here. I will publish it today (will go isolate myself in a coffee shop) and also owe you a beer or 2…
Ok, last attempt. Wondering if you ever posted something somewhere that I missed? Obrigado!
I think I am finally catching an understanding of using the accept value to version the representations. And I can certainly see the value of being able to version 1 specific method vs having to accept all changes that might belong to a new service version. That said — I would really like to hear/read what you have found out in relation to NuGet API. I just took a look and didn’t see another post that seemed to cover it — hate to pester, but is it still coming?
Just got back from vacation last night – so between that and the forthcoming holiday, should have something in a week or so…
Hi Howard, just wondering where you’re at with your post describing your thoughts on all of this?
Thanks for writing back! Fantastic, I look forward to reading it.
Aaron, these are some really great comments. As it turns out, I’ve been thinking a lot about this recently and have been having some conversations around the general weaknesses of purely server-driven content negotiation (for many of the reasons you mentioned). Rather than noting my thoughts in comments, I’m going to just do a new post describing where my thinking is at the moment regarding conneg and what that means for versioning/metadata/linking. Much of this thinking has been coming out of the NuGet API redesign effort, so should be able to provide some concrete examples behind the concepts.
Howard, this was a fantastic read and a nice compliment to your pluralsight video as well. Where things break down for me (and others that I know) is when we make the assumption that our consumers will be able to specify content types or version numbers in a request for a resource. Maybe you can chime in here. Drawing from your example, let’s say that your uri is as follows:
Performing a GET, with accept: text/json against the above api returns a data representation of Grace. So you might get back.
Performing a GET, with accept: image/jpg against the above api, will return an image of Grace.
So me as a consumer, might write a web page to consume that api. Assuming that I want the image representation of Grace, I would write the following HTML:
Where this breaks down is that I cannot specify the accept type in the image tag, so how can I be sure that the server will default my request to image/jpg?
Further, If i wanted version 2, I cannot specify that either. So I feel like there might actually be some validity in using a version based uri scheme for these types of scenarios.
Many times, consumers of your apis will just be the average browser user, who doesn’t have the first clue about HTML, so the only way for these users to access version 2 of grace’s image would be via api/v2/children/grace/image. Certainly they’re not going to figure out a way to specify a version type or accept type in the header.
So unless I’m missing something, it just feels like we have to consider our audience when designing our restful services.
I would REALLY love your thoughts on this. I think where RESTFul design practices break down for people is around the disparity in which they are consumed.
“but it seems an illogical default” & # 8211; it’s not an illogical default… it would be more illogical to return version 2.1 definitions.
Just got pointed to this from Mike Wasson’s blog post. A couple of queries on implementing this.
You’d presumably want to accept some standard mime type as well? Isn’t this slightly manipulating content negotiation more than the intended usage – your response isn’t going to be a different content type as such. If going this route, wouldn’t your preferred accept type be Content-Type: text/html;apiVersion=1.1?
In your closing you stipulate (I’m paraphrasing) that if you’re changing the meaning of the resource, change the URL. In most mature production systems this would mean having both versioning strategies in play – content type negotiation and URLs. Isn’t REST, and the API strategy in general, supposed to promote inferable APIs which is a little tricky if I have to reference /api/v2/person and supply a specific content type? i. e. I’d have to provide both the v2 in the URL and a v2 in the content type otherwise the next ‘breaking change to the representation’ (bullet point 2 in closing) that is made by the api would also break my client code as the logical default content-type, if using text/html, would be the ‘latest’ version of the api? Obviously this could be fixed by not deafulting to the latest version of the api but it seems an illogical default; my point is that mixing the two seems somewhat fraught.
Obrigado! I believe I have a lot to learn, but you provided a bunch of useful information.
so there are a few questions in here, some related to versioning, some related to HTTP, and some related to REST in general. I’ll try to address a couple points here, but very strong urge you to read the Fielding dissertation for a more comprehensive treatment of REST, as well as other architectural styles that REST compares/contrasts with.
“When you are using the accept header for versioning, aren’t you essentially extending the URI to include the header?”
Na verdade não. At the meta level, in this example the accept header (though content negotiation can be based on any bit of metadata, not just the accept header) is used to make a statement about the version of the *representation* whereas the URL identifies a resource. Practically, this means that that set of URLs that make up a service’s resource model can remain stable across multiple versions of changes in the representation (including the addition of new capabilities).
One thing that used to keep me from fully getting the concept of versioning representations like this was when I used to equate representation with entity (e. g. I have a bug representation, a customer representation, etc..). When I redefined representation (and the representation design) to include everything that could show up in the representation (Mike Amundsen calls these ‘state blocks’ in his book shop. oreilly/product/0636920020530.do), versioning the representation made a lot more sense wrt both the value in doing it and a practical approach.
“you are now requiring the REST server to now read the HTTP headers, instead of just the request”
At the risk of sounding like I’m nitpicking, there is no such thing as a REST server – REST is just a style of distributed systems architecture. I am using an HTTP server, though, and am quite confident that it knows how to read and process HTTP headers.
“it should be implemented over a protocol that can transfer a meaningful representational state, such as HTTP”
Based on my reading waaayyy too much into this statement, I think you’re conflating a couple different things together (specifically, the self-describing message constraint of REST with the definition of representation state with the definition of HTTP) – the dissertation should help here better than my comments on your comments.
‘Technically, this could be achieved in the HTTP request: ” HTTP/1.1″‘
I don’t see how you could realize the self-describing message constraint (or the stateless constraint or the caching constraint, etc.) of REST with a protocol version, resource identifier, and one piece of control flow data. The data elements in the RESTful style include resource metadata, representation metadata, and control data – which HTTP implements as headers. As I remember it, Fielding’s only issue with this design was that all of the different types of metadata were reduced to the same flat list of headers in HTTP, making it less apparent what header was what type of metadata. One comment he did make that validates something you mentioned was that he agreed that cookies were generally contrary to the principles of the RESTful style, as they reduced the visibility (e. g. self-describing messages) and in practice, tend to be used in ways that also undermine the stateless constraint.
Past that, I’m not sure that we’ve gotten carried away with using a major component of a protocol that was meant to be used as an application protocol.
Hope this long-winded response was helpful – thanks for commenting!
When you are using the accept header for versioning, aren’t you essentially extending the URI to include the header? I don’t see much of a difference between GET /bugs/v2 and GET /bugs with a header with v2, except you are now requiring the REST server to now read the HTTP headers, instead of just the request.
At the University of Minnesota, we have recently been discussing REST architecture in the Software Engineering program. I haven’t read Roy Fielding’s dissertation on REST yet, but I think I should. In my understanding of REST, it should be implemented over a protocol that can transfer a meaningful representational state, such as HTTP. Technically, this could be achieved in the HTTP request: ” HTTP/1.1″. But I think we took it too far by using HTTP headers (authentication, cookies, and in your example accept).
What do you think about this?
2 separate issues (‘things’ getting unwieldy and pretending that there is no network) – sorry if the way the post was worded made it seem like I was conflating the two.
Anyways, here’s the scenario I was thinking of wrt the ‘unwieldy’ comment. Because the unit of versioning is the service in the Web services world, then most of the changes that you make to to the service (e. g. a service operation) will require you to increment the service version. Coupled with the fact that services tend to be more coarsely grained means that you can very quickly end up with myhost/api/v2*10^8 (ok, that may be a *little* dramatic). Having this many versions is confusing to clients, but more importantly can become a pain to maintain on the server (e. g. how many interfaces does your service class end up implementing over time?).
There are plenty of problems with frameworks that enable you to pretend that there is no network (e. g. work with services as if they were local objects via proxies) – but in this post, I was thinking of the difficulty that can happen when the service versions change and clients need to rev to support the new service version – even if the client in question only needs the functionality of 1 operation/resource. In this case, even though the client is only using one new capability (or 1 bug fix), the client needs to test for everything that has changed in the new version.
Howard, I’m a bit confused about “It doesn’t take much imagination to see how unwieldy this can get after even a few iterations – especially when you’re clients are interacting with the service by generating strongly typed proxies and then pretending that there is no network”
I don’t get your point here…. “pretending there is no network”. Proxies will keep on hitting nuget/api/v1/ and everything is fine.. or what do I miss?
Esta é uma boa e uma pergunta complicada. O tópico de design de URI é ao mesmo tempo a parte mais proeminente de uma API REST e, portanto, um compromisso potencialmente de longo prazo com os usuários dessa API.
Como a evolução de um aplicativo e, em menor escala, de sua API é um fato da vida e é até similar à evolução de um produto aparentemente complexo como uma linguagem de programação, o design de URI deve ter menos restrições naturais e deve ser preservado ao longo do tempo . Quanto maior o tempo de vida do aplicativo e da API, maior o comprometimento com os usuários do aplicativo e da API.
Por outro lado, outro fato da vida é que é difícil prever todos os recursos e seus aspectos que seriam consumidos por meio da API. Felizmente, não é necessário projetar a API inteira que será usada até o Apocalypse. É suficiente definir corretamente todos os endpoints de recursos e o esquema de endereçamento de cada recurso e instância de recurso.
Com o tempo, você pode precisar adicionar novos recursos e novos atributos a cada recurso específico, mas o método que os usuários da API seguem para acessar determinados recursos não deve ser alterado quando um esquema de endereçamento de recursos se tornar público e, portanto, final.
Este método se aplica à semântica do verbo HTTP (por exemplo, PUT deve sempre atualizar / substituir) e códigos de status HTTP que são suportados em versões anteriores da API (eles devem continuar a funcionar para que os clientes da API que trabalharam sem intervenção humana possam continuar trabalhando) Curtiu isso).
Além disso, como a incorporação da versão da API na URI interromperia o conceito de hipermídia como o mecanismo do estado do aplicativo (declarado na dissertação de doutorado de Roy T. Fieldings) por ter um endereço de recurso / URI que mudaria com o tempo, concluiria que a API As versões não devem ser mantidas em URIs de recursos por um longo tempo, o que significa que os URIs de recursos dos quais os usuários da API podem depender devem ser permalinks.
Claro, é possível incorporar a versão da API no URI de base, mas apenas para usos razoáveis e restritos, como a depuração de um cliente de API que funciona com a nova versão da API. Essas APIs com versão devem ser limitadas no tempo e estar disponíveis apenas para grupos limitados de usuários da API (como durante betas fechados). Caso contrário, você se compromete onde não deveria.
Algumas considerações sobre a manutenção de versões da API com data de expiração. Todas as plataformas / linguagens de programação comumente usadas para implementar serviços da Web (Java,.NET, PHP, Perl, Rails, etc.) permitem uma vinculação fácil do (s) ponto (s) de extremidade do serviço da Web a um URI de base. Dessa forma, é fácil reunir e manter uma coleção de arquivos / classes / métodos separados em diferentes versões da API.
A partir do POV dos usuários da API, também é mais fácil trabalhar e vincular-se a uma determinada versão da API quando isso é óbvio, mas apenas por tempo limitado, ou seja, durante o desenvolvimento.
A partir do POV do desenvolvedor da API, é mais fácil manter diferentes versões da API em paralelo usando sistemas de controle de origem que trabalham predominantemente em arquivos como a menor unidade de versão (código-fonte).
No entanto, com as versões da API claramente visíveis no URI, há uma ressalva: também é possível objetar essa abordagem, já que o histórico da API se torna visível / aparente no design da URI e, portanto, está sujeito a mudanças ao longo do tempo que vai contra as diretrizes do REST. Concordo!
A maneira de contornar essa objeção razoável é implementar a versão mais recente da API sob URI de base da API sem versão. Nesse caso, os desenvolvedores de clientes da API podem optar por:
desenvolver contra o mais recente (comprometendo-se a manter o aplicativo protegendo-o de eventuais alterações de API que possam quebrar seu cliente de API mal projetado).
ligar a uma versão específica da API (que se torna aparente), mas apenas por um tempo limitado.
Por exemplo, se a API v3.0 for a versão mais recente da API, as duas seguintes devem ser aliases (ou seja, comportar-se de forma idêntica a todas as solicitações da API):
Além disso, os clientes da API que ainda tentam apontar para a API antiga devem ser informados para usar a versão da API anterior mais recente, se a versão da API que eles estiverem usando for obsoleta ou não for mais suportada. Então, acessando qualquer um dos URIs obsoletos como estes:
deve retornar qualquer um dos códigos de status HTTP 30x que indicam o redirecionamento usado em conjunto com o cabeçalho HTTP de local que redireciona para a versão apropriada do URI do recurso que permanece como esta:
Existem pelo menos dois códigos de status HTTP de redirecionamento que são apropriados para cenários de versão de API:
301 Movido permanentemente, indicando que o recurso com um URI solicitado é movido permanentemente para outro URI (que deve ser um permalink de instância de recurso que não contém informações sobre a versão da API). Esse código de status pode ser usado para indicar uma versão de API obsoleta / sem suporte, informando ao cliente de API que um URI de recurso com versão foi substituído por um link permanente de recurso.
Seu versionamento de API está errado, e é por isso que decidi fazer isso de três formas diferentes e erradas.
No final, decidi que a maneira mais justa e equilibrada era irritar todo mundo igualmente. É claro que estou falando sobre versionamento de APIs e não desde as ótimas guias # x201C versus espaços & # x201D; debate tenho visto tantas crenças fortes em campos totalmente diferentes.
Isso foi ótimo. Quando eu construí Eu fui pwned? (HIBP) no final de novembro, foi concebido para ser um serviço simples e rápido que algumas pessoas usariam. Eu acho que é justo dizer que os dois primeiros pontos foram alcançados, mas não o último. Não era um "número um", na verdade, no final da primeira semana, era mais do que o Google Analytics poderia suportar. O ponto é que você não pode sempre prever o futuro quando você escreve sua API e em algum momento você pode precisar mudar algo que as pessoas já dependem.
Mas aqui está o problema & # x2013; toda vez que você começa a falar sobre qualquer coisa relacionada a APIs via HTTP, isso acontece:
Todo caminho que você vira, há diferentes filosoficos sobre o caminho certo & # x201D; e muito para trás e para frente no REST, o que é RESTful, o que não é e se é importante. Vamos falar sobre as alterações da API, o impacto sobre as versões, por que há tantas idéias divergentes sobre como isso deve ser feito e, por fim, por que nenhuma das brincadeiras é tão importante quanto realmente fazer as coisas.
Puxando mais dados de violação.
Tendo em mente que a mesma API é usada para o recurso de pesquisa no site e agora também por terceiros criando tudo, de aplicativos de smartphone a ferramentas de teste de penetração, a resposta acima funcionou bem no início, mas foi limitada. Por exemplo, esta resposta não funciona tão bem:
Por quê? Porque & # x201C; BattlefieldHeroes & # x201D; é o Pascal-cased que é ótimo para combinar com classes CSS codificadas (embora provavelmente não seja uma boa abordagem a longo prazo) e por ter um & # x201C; stable & # x201D; nome para se referir a (eu não vou alterá-lo, mesmo que haja uma segunda violação), mas não é adequado para exibição como um título. Tudo isso sai do Armazenamento de Tabelas do Azure e eu entro no SQL Azure para extrair dados relacionais que realmente descrevem a violação. Um dos atributos nesse armazenamento relacional é o nome que você vê acima.
O que eu realmente queria fazer era algo mais assim:
Pegue? Para o ponto anterior sobre o nome da violação, que ainda está lá no atributo name, mas agora temos um título também. Isto é o que você mostra para as pessoas & # x2013; & # x201C; Battlefield Heroes & # x201D; & # x2013; mas mais importante, se o Gawker for penhorado novamente, posso nomear a violação de algo como Gawker2014 e o título pode ser algo amigável ao longo das linhas de Gawker (Ataque Eletrônico do Exército Sírio) & # x201D ;. Ele segmenta o que é estável e previsível daquilo que não é e significa que as pessoas podem criar dependências, como imagens ou outros recursos, no atributo name.
Os outros dados devem ser bem claros: a data da violação, quando foi adicionada ao sistema, o número de contas pwned, a descrição da violação (novamente, isso pode mudar se o palavreado precisar ser ajustado) e & # x201C; DataClasses & # x201D ;. Uma das coisas que muitas pessoas estavam pedindo era uma descrição do que estava comprometido na brecha, então agora há um monte de atributos que podem ser adicionados através de uma coleção sobre a violação em si. Eu já estou mostrando isso abaixo de cada violação na página dos sites da Pwned (essa é outra razão pela qual eu posso agora ajustar algumas das descrições).
Esta é uma mudança urgente. Enquanto o sentimento da API é o mesmo & # x2013; forneça um nome de conta, receba de volta uma lista de violações & # x2013; não há mais uma matriz de strings de nomes de violações. Se eu simplesmente substituísse a API antiga por essa, as coisas iriam quebrar. APIs. Devo. Evoluir.
O software evolui, as APIs devem ser versionadas.
Vamos ser sinceros sobre isso: o mundo segue em frente. A API para o HIBP durou cerca de 2 meses, não porque foi mal projetada, mas porque o serviço se tornou descontrolado e inesperadamente bem-sucedido. Eu gosto desse tipo de problema, e você também deveria.
Agora eu tive uma escolha; ou eu poderia me contentar com o que eu tinha e privar as pessoas de uma maneira melhor, eu poderia adicionar ao serviço existente de uma forma não violenta ou eu poderia criar uma nova versão (embora expondo a mesma entidade de uma maneira diferente) e construir É a melhor maneira de saber como; sem bytes desnecessários, modelados corretamente (até que eu decida que uma nova versão é mais correta) e uma boa representação da entidade que eu estou tentando finalmente entrar nos consumidores & # x2019; apps.
Não há nada de errado em introduzir uma nova versão de uma API quando é a coisa mais sensata a ser feita. Por todos os meios, faça o seu melhor para obtê-lo & # x201C; direita & # x201D; desde o primeiro dia, mas fazê-lo com a expectativa de que "certo" & # x201D; é um estado temporário. É por isso que precisamos estar aptos para a versão.
Os vários campos de versionamento.
Certo, então quão difícil pode ser esse negócio de versionamento? Quero dizer, deve ser um exercício simples, certo? O problema é que isso é muito filosófico, mas em vez de ficar atolado nisso por enquanto, deixe-me delinear as três escolas comuns de pensamento em termos de como elas são praticamente implementadas:
URL: basta digitar a versão da API no URL, por exemplo: haveibeenpwned / api / v2 / breachedaccount / foo Cabeçalho de solicitação personalizada: você usa o mesmo URL de antes, mas adiciona um cabeçalho como & # x201C; api-version: 2 & # x201D; Aceitar cabeçalho: você modifica o cabeçalho de aceitação para especificar a versão, por exemplo, & # x201C; Accept: application / vnd. haveibeenpwned. v2 + json & # x201D;
Tem havido muitas, muitas coisas escritas sobre isso e eu vou linkar para eles no final do post, mas aqui está a versão abreviada:
Os URLs são uma droga porque devem representar a entidade: na verdade, eu concordo com isso na medida em que a entidade que estou recuperando é uma conta violada, não uma versão da conta violada. Semanticamente, não é realmente correto, mas é fácil de usar! Cabeçalhos de pedidos personalizados são uma droga porque não é realmente uma forma semântica de descrever o recurso: A especificação HTTP nos dá um meio de solicitar a natureza que gostaríamos do recurso representado por meio do cabeçalho de aceitação, por que reproduzir? esta? Aceitar cabeçalhos chupados porque são mais difíceis de testar: não posso mais apenas fornecer a alguém um URL e dizer: "##201C; aqui, clique em" & # x201D ;, em vez disso, eles devem construir cuidadosamente a solicitação e configurar o cabeçalho de aceitação adequadamente .
Os vários argumentos a favor e contra cada abordagem tendem a ir de & # x201C; este é o & # x2018; certo & # x2019; maneira de fazer isso, mas é menos prático & # x201D; através de & # x201C; Esta é a maneira mais fácil de criar algo consumível que, portanto, faz com que seja "# & # x2018; right & # x2019; & # x201D ;. Há muita discussão sobre hipermídia, negociação de conteúdo, o que é & nbsp; REST & # x201D; e todo tipo de outras questões. Infelizmente, isso muitas vezes é filosófico e perde a visão de qual deve ser o objetivo real: construir um software que funcione e particularmente para uma API, tornando-a facilmente consumível.
É sobre ter um contrato estável, estúpido!
Mais importante do que todas as reclamações e delírios sobre como fazer isso dessa maneira ou daquela maneira é dar estabilidade às pessoas. Se eles investem seu esforço suado escrevendo código para consumir sua API, então é melhor que você não a interrompa mais adiante.
Honestamente, os debates sobre o que é o & # X201C; RESTful & # x201D; contra o que não é como se o próprio termo ditasse seu sucesso é apenas louco. Transforme essa discussão em "Aqui estão as razões práticas pelas quais isso faz sentido, e é isso que pode acontecer se você não o fizer", e eu farei de tudo. O problema é que até mesmo as vozes da razão dentro das discussões barulhentas deixam dúvidas quanto ao que realmente é a melhor abordagem e, portanto, eu alcancei um compromisso & # x2026;
Aqui estão 3 maneiras erradas de consumir a API de HIBP que você pode escolher agora.
Ok, agora que estamos claramente estabelecidos, mas você está errado, eu gostaria de dar a você a escolha de escolher qualquer uma das três formas erradas. Espere & # x2013; o que?! É assim: no entanto, eu implemento a API, ela será muito difícil de consumir, muito acadêmica, muito provavelmente falha no proxy ou algo do tipo. Em vez de escolher um caminho errado, decidi dar-lhe todas as 3 formas erradas e pode escolher aquele que é o menos errado para você.
Caminho errado 2 - cabeçalho de solicitação personalizada:
Inevitavelmente, alguém vai me dizer que fornecer 3 formas erradas é a coisa errada a fazer. Não significaria mais código kludge para manter? Não, isso significa simplesmente que a implementação da API da Web subjacente é decorada com dois atributos:
O primeiro é simplesmente uma restrição de roteamento que implementa o RouteFactoryAttribute. Eu passo na rota e passo a versão que pode mapear para aquela rota, então a implementação procura a presença de um & # x201C; api-version & # x201D; cabeçalho ou um cabeçalho de aceitação correspondente a esse padrão:
Se a versão especificada em uma dessas combina com a especificada na restrição de roteamento, então é o método que será invocado. Esta é uma adaptação simples desta amostra no CodePlex.
O segundo atributo que decora o método GetV2 acima é cortesia da Web API 2 e do recurso de roteamento de atributos. É claro que sempre poderíamos fazer roteamento na API da Web, mas isso geralmente era definido globalmente. O roteamento de atributos como esse traz a definição de rota para o contexto em que ela é aplicada e facilita a visualização da ação do controlador que será chamada por qual rota. Isso também significa que as implementações de todas as três formas erradas de chamar a API estão reunidas em um único local.
Então, em suma, não, isso não cria um monte de kludge e é muito fácil de manter. Cada uma das três abordagens retornará exatamente o mesmo resultado e, o mais importante, elas permanecerão estáveis e não serão alteradas de nenhuma forma e, no final das contas, será a mais importante. importante, independentemente de qual opção você escolher. Toda a implementação agora também está claramente documentada na página da API do site.
Mas e se você não especificar uma versão?
Você sabe o pouco onde eu disse que você não pode quebrar o que já está lá fora? Sim, isso significa que se você fizer o que faz agora, # x2013; não especifique uma versão & # x2013; então você começa o que você recebe agora. Em outras palavras, nenhum pedido para uma versão específica significa que você obtém a versão 1.
Eu estou bem com isso, independentemente de ter atingido este ponto por padrão. Eu sei que algumas pessoas sempre gostam de retornar a versão mais recente se um número não for especificado, mas IMHO que quebra todo o contrato estável & # x201D; # x201D; objetivo; o que você obtém da API hoje pode ser completamente diferente do que você recebe amanhã se eu revisá-lo. Isso seria uma droga e quebraria as coisas.
Você tem 3 opções, mas minha preferência pessoal é & # x2026;
Eu tenho o luxo de controlar tanto a API quanto o consumidor primário do site da HIBP. Dado que eu forneci 3 opções para consumir a API, qual delas eu mesmo uso?
Eu fui com o favorito filosófico que é especificá-lo através do cabeçalho de aceitação. Eu não acho que isso é certo e os outros estão errados, ao contrário, eu acho que isso faz mais sentido por duas razões principais:
Concordo que o URL não deve mudar: se concordarmos que o URL representa o recurso, a menos que estejamos tentando representar versões diferentes do próprio recurso, não, não acredito que o URL deva mudar. As brechas para foo são sempre as brechas para foo e eu não acho que só porque eu mudo os dados retornados para foo que a localização de foo deve mudar. Concordo que os cabeçalhos de aceitação descrevem como você deseja os dados: Esta é uma semântica da especificação de HTTP e assim como a semântica dos verbos de solicitação faz muito sentido (isto é, estamos obtendo, colocando ou excluindo ou postando), O mesmo acontece com a maneira como o cliente gostaria que o conteúdo fosse representado.
De maneira nenhuma isso significa que eu acho que os outros dois estão errados e, francamente, não há melhor maneira de compartilhar a API com alguém do que dizer: "Aqui, clique aqui, mas quando eu puder facilmente construir o pedido e gerenciar os cabeçalhos, eu fui com esta rota.
Na verdade, pensando nisso, eu também uso a versão na rota do domínio. Por quê? Apenas através do processo de escrever esta API eu estava constantemente me comunicando com as pessoas sobre as formas de consultá-las (mais sobre isso mais tarde) e os atributos que ela retorna. Ser capaz de passar por um e-mail e dizer "Ei, aqui está o que eu estou pensando" ##201D; e eles simplesmente clicam e obtêm resultados é inestimável. Esse é o ponto que os proponentes da abordagem de versionamento de URLs fazem com toda a razão: você simplesmente não pode fazer isso quando estiver dependente de cabeçalhos.
Ah, e no caso de você estar me checando, no momento em que escrevo, eu ainda não rolei o site para a v2 da API. Agora que os dados de violação são extraídos na API quando ocorre uma pesquisa, isso significa que eu tenho o luxo de não carregar todas as violações na origem na carga inicial (isso nunca será sustentável à medida que o conjunto de dados se expande). Isso salvará um monte de tráfego de saída e acelerará as coisas para as pessoas em termos de obter o site carregado, mas isso também significa um pouco mais de trabalho do meu jeito. Fique ligado.
No fechamento.
Claramente eu tenho sido um pouco irônico aqui no que diz respeito a tudo estar errado, mas honestamente, quanto mais você lê sobre isso e quanto mais perguntas você faz, mais errado todo caminho parece de uma maneira ou de outra. Na verdade, eu sei muito bem que existem aspectos da minha implementação que serão referidos como "errados" e "x201D". (Eu posso pensar em pelo menos um par) e, naturalmente, eu estou me preparando para o potencial ataque de feedback para esse efeito. A coisa é, porém, cada uma dessas opções funciona e, francamente, para todos os efeitos práticos, eles funcionam tão bem quanto os outros.
Se eu puder deixar outras pessoas pensando em como atualizar suas APIs com um pensamento final: ninguém usará sua API até que você a tenha criado. Pare de procrastinar. Nenhuma dessas opções é "ruim" e "x201D"; em qualquer sentido tangível, eles são apenas diferentes. Eles são todos facilmente consumíveis, todos eles retornam o mesmo resultado e nenhum deles é susceptível de ter qualquer impacto real sobre o sucesso do seu projeto.
Referências.
Stack Overflow: Práticas recomendadas para o versionamento de API? (ótima pergunta, ótimas respostas, fechada como "não construtiva", eu assumo porque "Bill the Lizard & # x201D; saiu do lado errado da cama naquela manhã) Blog do Lexical Scope: How are REST APIs com versão? (boa comparação de práticas de controle de versão entre serviços, ainda que alguns anos atrás) CodePlex: Exemplo de restrição de roteamento (vinculado na página da API da Web da Microsoft como um exemplo de APIs de controle de versão adicionando um cabeçalho personalizado) CodeBetter: Versionamento RESTful Serviços (muito pragmáticos e uma boa descrição das várias maneiras pelas quais uma API pode mudar) Blog de Vinay Sahni: Práticas recomendadas para projetar uma API RESTful pragmática (ele está argumentando sobre o versionamento de URL por causa de & # x201C ; explorabilidade do navegador & # x201D;) Lans Pivotal: versionamento da API (boa visão das opiniões conflitantes existentes) Pilha Web de Amor: ASP. NET Web API Versioning com Tipos de Mídia (bom passo-a-passo de criar um aplicativo para suportar versionamento por negociação de conteúdo)
Oi, sou Troy Hunt, escrevo este blog, crio cursos para a Pluralsight e sou diretor regional da Microsoft e MVP que viaja pelo mundo falando em eventos e treinando profissionais de tecnologia.
Oi, sou Troy Hunt, escrevo este blog, crio cursos para a Pluralsight e sou diretor regional da Microsoft e MVP que viaja pelo mundo falando em eventos e treinando profissionais de tecnologia.
Próximos eventos.
Eu normalmente faço workshops particulares em torno destes, eis os próximos eventos públicos em que estarei:
Insights de versão de API RESTFul.
Quando se trata de versionamento de APIs, existem muitas práticas recomendadas e insights, mas ainda não há uma prática recomendada sólida.
Para entender o versionamento da Restful API, primeiro precisamos entender o problema.
O problema de versionamento.
Ninguém deve quebrar seus clientes.
Mudar sua API é uma coisa bem fundamental de se fazer. Você está literalmente mudando a interface com a qual os clientes estão se comunicando antes da sua mudança. O ponto é que você não quer quebrar os clientes que se conectam a você hoje, embora ainda precisem alterar (adicionar, remover ou alterar) sua API. Isto não afeta apenas a aparência da API, ou seja, os formatos de solicitação ou resposta também podem incluir funcionalidade, por exemplo. os padrões agora funcionam de maneira diferente.
Infelizmente, não importa o quão brilhante você arquitetar sua solução, você pode achar que, ao longo do tempo, você tem que alterá-lo.
Pode-se argumentar que os micro-serviços contornam esse problema por serem pequenos. Micro-serviços devem ser tão pequenos que são tão simples que você não terá espaço para mudá-los - você pode ler mais sobre o que são micro-serviços, mas não vou falar sobre eles hoje.
Então, supondo que você tenha que alterar sua API, o que você realmente quer fazer é garantir que seus clientes saibam que sua API mudou e dar a eles alguma forma de decidir programaticamente, ou de outra forma, para qual versão eles se conectarão. que eles continuem trabalhando.
Agora que entendemos o problema, quais são as soluções possíveis?
As 4 possíveis soluções para versionamento de API.
Antes de dizer mais, gostaria de dizer que cada opção tem seus prós e contras. Qual deles você acaba escolhendo pode ser afetado não apenas pelas melhores práticas, restrições da infraestrutura e outros aspectos. Atualmente, não há a melhor solução para o Restful API Versioning, e até mesmo aqueles que acreditam que não devemos precisar de versões de nossas APIs, como esta postagem "Don 't Version Your Web API" de Jan Stenberg.
Nesse método, a versão é colocada explicitamente no URI da API. Por exemplo:
. / maps / version / 2. / maps / version / 2 / buildings / version / 3. / maps / v2 / buildings.
As opções acima mostram três maneiras diferentes de expor as versões por meio do seu URI.
Em primeiro lugar, um modelo de hierarquia grosseira de granulação grossa e, em seguida, um método de subtópicos mais refinado, que nos dá a capacidade de desenvolver elementos separados da API (estradas, neste caso). A terceira opção mostra apenas o modelo um pouco menos expressivo de ter um único argumento de versão (por exemplo, 'v2') sem o nó explícito de 'versão' na hierarquia de URL.
Eu, pessoalmente, não gosto desse modelo. Do ponto de vista mais puro, argumenta-se que o URI em REST deve representar apenas a estrutura de recursos.
Uma versão não é um recurso, é um atributo do recurso.
No entanto, em um lado positivo, posso ver como é muito claro o que está acontecendo! Também vejo isso sendo recomendado por muitos fornecedores de ferramentas de API. Aqui estão mais alguns prós e contras para este método específico.
Já podemos ver como as vantagens para uma pessoa podem ser vistas como desvantagens para outra.
URIs sugam porque devem representar a entidade - eu quero recuperar a entidade de mapas, não uma versão da conta violada. Semanticamente, não é realmente correto, mas é muito fácil de usar!
2) O cabeçalho Accept.
Há um cabeçalho HTTP conhecido, chamado Accept, que é enviado em uma solicitação de um cliente para um servidor. Por exemplo.
Esta notação está dizendo que eu, o cliente, gostaria que a resposta estivesse no json por favor.
Os cabeçalhos de aceitação estão usando esse cabeçalho para criar seus próprios tipos de recursos, por exemplo:
Aceite: application / vnd. myapi. v2 + json.
Agora; espere aqui porque essa é uma sintaxe um pouco estranha que nos encontramos olhando, então vamos analisá-la.
As especificações da Internet dizem que eu, como fornecedor, posso definir (e registrar) um tipo de mídia. Se eu fizer isso, é chamado (sem surpresa) um tipo de mídia 'fornecedor'. Eu deveria prefixar meu tipo com 'vnd' para deixar claro que é minha própria definição. Então preciso dizer qual é o nome do tipo real que estou definindo, e. 'myapi'. No entanto, a especificação não informa nada sobre um número de versão, então as pessoas começaram a dizer que o nome do tipo de mídia inclui um número de versão, por exemplo:
Agora, como eu, como aplicativo cliente, ainda preciso definir qual tipo de conteúdo eu realmente quero (além da versão), isso pode ser adicionado como um sufixo ao tipo de mídia solicitado, por exemplo. '+ json' neste caso.
Observe que há um método alternativo de fazer isso sem qualquer pré-registro do tipo de mídia usando x. como o prefixo:
O uso do cabeçalho Accept parece um pequeno truque da especificação para mim - mas funciona e é bem conhecido, se não for realmente especificado como tal.
O maior problema com esse método é que ele é bastante oculto - e as coisas ocultas são sempre um pouco mais difíceis de se trabalhar. É quase sempre melhor ser explícito no que você está fazendo. Também suspeito que pedir ao seu administrador de firewall para abrir o firewall deles para qualquer tipo de mídia antigo pode levar a um grande risco de segurança.
No entanto, parece muito mais fácil passar o cabeçalho Accept: vnd do que passar cabeçalhos de solicitação customizados.
Aceitar cabeçalhos, porque são mais difíceis de testar - não posso mais apenas fornecer a alguém um URL e dizer "aqui, siga este link", em vez disso, eles precisam construir cuidadosamente a solicitação e configurar o cabeçalho de aceitação adequadamente.
3) cabeçalho de pedido personalizado.
Nas especificações HTTP originais, você pode definir qualquer cabeçalho HTTP antigo que você gostou na solicitação do cliente, desde que você tenha pré-fixado com "X", por exemplo
As especificações mudaram e você ainda pode passar qualquer cabeçalho de pedido antigo, mas ele não precisa mais ser pré-fixado com "X".
Isso significa que você pode pedir aos clientes da sua API para passarem algo assim.
Esta opção não é recomendada nem usada muito devido aos seguintes motivos:
Alguns dos roteadores (embora hoje as coisas sejam diferentes e a maioria deles transmitirá todos os cabeçalhos) podem rejeitar toda a solicitação http ou simplesmente não passar o cabeçalho especial. Depurar isso pode ser uma coisa muito difícil de fazer. Eu tive a mudança para encontrar uma coisa dessas, onde algumas máquinas estavam se conectando através de diferentes roteadores e um roteador em particular tinha sido configurado para remover tipos de cabeçalho não padrão. Permitiu que a mensagem passasse - incluindo todos os outros cabeçalhos - mas apenas silenciosamente removeu os que não gostou!
Está escondido novamente - assim como o cabeçalho Accept. O cabeçalho Accept já é uma maneira de ser bem explícito sobre o que o cliente aceita, então se vamos esconder coisas, pelo menos vamos usar um jeito que é conhecido.
Cabeçalhos de solicitações personalizadas são uma droga, porque não é uma maneira semântica de descrever o recurso. A especificação HTTP nos dá um meio de solicitar a natureza que gostaríamos que o recurso fosse representado por meio do cabeçalho de aceitação. Por que reproduzir isso?
4) O Parâmetro URI.
Aqui está uma óbvia que eu não vejo muitas pessoas usando:
Este método é muito usado pela Amazon, Google e Netflix, mas ainda não é tão popular. Hmmm - talvez haja algo para isso ?!
Versões de pensamento desde o primeiro dia.
Com isso quero dizer - quando você cria sua API atual, acredita que ela mudará, mesmo que não seja hoje - algum dia. Isso afetará o que você codifica - não apenas suas decisões sobre onde colocar o número da versão.
Se você é um desenvolvedor ágil, então pode estar gritando na tela agora mesmo.
Ágil é tudo sobre hoje. Na verdade, diz especificamente - não codifique para coisas que não estão na especificação. No entanto, vamos ser claros: codificar para extensibilidade não é o mesmo que colocar as extensões em si - que é sobre o que a metodologia ágil fala.
Deixando espaço para futuras versões se encaixa muito bem ágil.
Versão de uma API é difícil!
Sim, é, no entanto, realmente não é mais difícil do que manter o código do cliente e do servidor em sincronia. You should remember that Building Restful APIs is much easier that SOAP web services and is supported pretty much by all.
Try to allow for extensibility in your code from day one.
With regard to how to specify the version - If you do have a versioning convention in mind then make sure that your infrastructure can handle it.
Read more posts by this author.
Compartilhe este post.
Subscribe to REST API and Beyond.
Get the latest posts delivered right to your inbox.
or subscribe via RSS with Feedly!
RESTful API Basic Guidelines.
Your data model has started to stabilize and you're in a position to create a public API for your…
REST API vs SOAP Web Services Management.
Back in the day Web Services were the de facto standard for accessing "systems of record". SOAP web services…
Versioning RESTful Services.
I’ve talked about this in various venues and also cover it in my Pluralsight REST Fundamentals course, but the topic of how to version RESTful services has been popping up a bunch recently on some of the ASP. NET Web API discussion lists, and my friend Daniel Roth asked if I could serialize some of that presentation content into a blog post – so here goes.
First, note that while the focus here is on RESTful services and not just HTTP services, the same principles can potentially apply to HTTP services that are not fully RESTful (for example, HTTP services that do not use hypermedia as a state transition mechanism).
When talking about versioning, the most important question to ask is “what are you wanting to version?” The logical extension to this question is, “what is the contract between your service and client?” This is naturally important since the contract is the thing you want to version.
In the “old world” of Web services, the contract was the service. Service actions (and associated semantics) along with data formats and other metadata were covered by the definition of the service, which was exposed as a single URL (the service, that is – I’m grouping together all RMM L0 services here). As such, when it came to the question of how to version the service, the answer was generally pretty simple: if the contract is the service, and the service is exposed as a URL, then the solution is to version the URL. As such, you’ll see a lot of this if you browse around –
(Not trying to pick on NuGet here – it just happens to be a service API that I’m pretty familiar with at the moment)
It doesn’t take much imagination to see how unwieldy this can get after even a few iterations – especially when you’re clients are interacting with the service by generating strongly typed proxies and then pretending that there is no network (yes, I am picking on WCF here).
So how is this different for RESTful services?
Well, we should start by again asking the question, “What is the contract for a RESTful service?” The answer is, IMHO, the uniform interface. The uniform interface is made up of 4 constraints –
Identification of resources Manipulation through representations Self-descriptive messages Hypermedia as the engine of application state (HATEOAS)
While all of these constraints are important to understand and take into consideration for the overall design of a RESTful system, I’m going to highlight the first 2 with respect to versioning.
First, let’s look at resources. A resource is any named information or concept – this is nice from a philosophical perspective, but maybe a little less helpful in how it enables us to think about versioning, so let’s look at it another way (as Fielding describes). A resource is a 0..n mapping between an identifier and a set of entities that changes over time. Here’s a concrete example:
I have 3 daughters: Grace, Sarah, and Abigail. However, this was not always the case – in fact, during the time period before Grace was born, the resource map of my family looked like the following:
As you can see, I had defined (in my mind) a bunch of named concepts – and at that point in time, they didn’t map to any actual entities (e. g. kids). Now, when Grace was born, the resource map looked like the following:
As you can see here, all of my named concepts map to a single entity – Grace. But what about when Sarah came along? Then the map changed to the following:
As you can now see, my “children” collection resource maps to multiple entities, and “youngest child” now maps to Sarah rather than Grace.
The point here is that the resource *concept* has not changed here – and more importantly, though the specific entity mappings have changed over time, the service has done this in a way that preserves the meaning of identified domain abstraction (e. g. children).
A representation, on the other hand, is an opaque string of bytes that is effectively a manifestation of a resource. Representations can come in many different formats and the process of selecting the best format for a given client-server interaction is called content negotiation. The self-descriptive message constraint of the uniform interface adds that the information needed to process a representation, regardless of format, is passed in the message itself.
I wanted to give this brief explanation of resources and representations because it’s important to have a clear understanding of what they are so that you can know when to version them. So let’s get back to versioning…
So, if the contract for a RESTful service is the uniform interface, then the answer to the question of how to version the service is “it depends on which constraint of the uniform interface you’re changing.” In my experience, there are 3 common ways that you can version (I’m sure there are more, but these are the 3 that I’ve come across most regularly).
Versioning Strategy 1: Adding content to a representation.
In the case where you’re adding something to a representation – let’s say that you’re adding a new data field “SpendingLimit” to a customer state block as follows:
“Name”: “Some Customer”,
In this case, the answer to the versioning question is to just add it. Now, this assumes that your clients will ignore what they don’t understand. If you’ve written clients in such a way that you can’t make that assumption, then you should fix your clients J – or perhaps you need to look at the next strategy…
Versioning Strategy 2: Breaking changes in a representation.
In the case where you’re either removing or renaming content from an existing representation design, you will be breaking clients. This is because even if they are built to ignore what they don’t understand, by making this sort of change on the server, you’re changing what they already understand. In this case, you want to look at versioning your representation . HTTP provides a great facility for doing this using content negotiation. For example, consider the following:
GET localhost:8800/bugs HTTP/1.1.
This request gives me the following response fragment – as you can see, I’m working from an HTML base media type:
Now what if, for some reason, I needed to change the link relationship values? Remember that based on the hypermedia constraint of the uniform interface, my client needs to understand (e. g. have logic written against) those link relationship values, so renaming them would break existing clients. However, in this case, I’m not really changing the meaning of the resources or the entities that the resources map to. Therefore, I can version my representation and enable clients that know how to work with the newer version to request the newer version using the HTTP accept header as follows:
Therefore, this request:
GET localhost:8800/bugs HTTP/1.1.
Will now give me the new response format:
One other thing that I want to mention here – you’ve probably noticed that I’m using the language of representation design and representation versioning as opposed to content type design/versioning. This is deliberate in that many (most?) times, you’re going to design your representations completely on top of existing content types (e. g. xml/json/html/hal/etc). Without wading into the custom media type debate in this post, my point is that when I’m talking about versioning the representation here, I’m talking about versioning the domain-specific aspects of your representation that your client needs to be aware of.
Versioning a representation over an existing media type will look slightly differently from what’s shown above in that you’ll pass a standard media type identifier in the HTTP accept header along with an additional metadata element to identify your representation-specific aspects and then do content negotiation based on the combined description. There are several different ways to add the representation-specific metadata, including media type parameters and custom HTTP headers.
Versioning Strategy 3: Breaking the semantic map.
In both of the prior strategies, all changes, breaking and non-breaking, have been related to the representations. This is a really good thing as it enables the resources (and more importantly, the URL resource identifiers) to remain stable over time. However, there may be occasions – hopefully rarely – when you need to break the meaning of a resource and therefore the mapping between the resource and its set of entities. As an example, as I get older and my kids grow up and leave, let’s say that I start returning my pets as children. In this case, I’ve changed the meaning of the “children” resource and thereby broken that aspect of the contract between my client and service. The solution then, in this case, is to version the resource identifier itself.
If I did a lot of this sort of resource versioning, it is very possible that I could end up with an ugly-looking URL space. But REST was never about pretty URLs and the whole point of the hypermedia constraint is that clients should not need to know how to construct those URLs in the first place – so it really doesn’t matter whether they’re pretty or ugly – to your client, they’re just strings.
So to summarize.
This post ended up being longer than I had planned, so here’s the summary.
In REST, the contract between clients and services is the uniform interface How you version depends on what part of the uniform interface you’re changing If you’re adding only, go ahead and just add it to the representation. Your clients should ignore what they don’t understand If you’re making a breaking change to the representation, version the representation and use content negotiation to serve the right representation version to clients If you’re changing the meaning of the resource by changing the types of entities it maps to, version the resource identifier (e. g. URL)
About Howard Dierking.
I also think that version in the URL is a part of base URL from client’s point of view, so basically changing version does not change semantics for the resources, but only response (representation). When you request GET /api/v1/books or GET /api/v2/books it’s like requesting GET /api/books with client designed for using with v1 and v2 respectively. You don’t hardcode version in client, you configure it from the outside and use relative resources (for example /books ) so the only problem here would be misconfiguration (e. g. using v1 client with v2 resources).
Sorry for resurrecting such old thread, but I must ask: did you write down that new post ;-)?
In my opinion in your second approach you are confusing representation with entity structure. Representation is about the format in which you send the information or your client expects to receive it (xml, html, json, etc), in this case the resource that you are pointing to. Which means if I request the same resource but with different representation (xml, json, etc) at the same instant of time then my resources should point to the same exact entity structure and contents but with a different format, the thing that changes is the way it is dressed.
If you add a new field to the entity your resource is mapping to (like in the first approach you describe: adding SpendingLimit to customer entity), what you are really doing is creating a new version of your entity with its own set of properties (in this case with all the properties from the previous plus a new one), then you need to create a new resource (or redefine the mappings) to map to that entity as you do in your children example when having a new child.
This is similar to what Roy Fielding describes in his blog when talking about bulk operations: …If you find yourself in need of a batch operation, then most likely you just haven’t defined enough resources… (roy. gbiv/untangled/2008/rest-apis-must-be-hypertext-driven#comment-743). In the sense that changes to the information structure (in this case number of entities mapped by the resource) is about identifying those entities by mapping them to a proper resource.
Based in all that, in my opinion and experience versioning a REST API in terms of changing entity structure should be done in the URI, but at the level of resource id rather than at the level of API, which means later and not earlier in the URL.
Doing it this way allows the client to request specifically the resource they want which maps to the entity they need. Without having to mess with headers and custom media types which is really problematic when implementing in a production environment.
‘especially when you’re clients’ should probably read ‘especially when your clients’
Must take issue with the “before Grace was born, the resource map of my family looked like the following” showing a Grace node (not subscribing to “a boy named Sue” or “the grace of God” unconditionally), e. g. AFTER birth of Sarah the Sarah node appears.
haha, no worries. I like beer a great deal, but I live in WI. So…. there’s that.
clearly, I’ve done a terrible job here. I will publish it today (will go isolate myself in a coffee shop) and also owe you a beer or 2…
Ok, last attempt. Wondering if you ever posted something somewhere that I missed? Obrigado!
I think I am finally catching an understanding of using the accept value to version the representations. And I can certainly see the value of being able to version 1 specific method vs having to accept all changes that might belong to a new service version. That said — I would really like to hear/read what you have found out in relation to NuGet API. I just took a look and didn’t see another post that seemed to cover it — hate to pester, but is it still coming?
Just got back from vacation last night – so between that and the forthcoming holiday, should have something in a week or so…
Hi Howard, just wondering where you’re at with your post describing your thoughts on all of this?
Thanks for writing back! Fantastic, I look forward to reading it.
Aaron, these are some really great comments. As it turns out, I’ve been thinking a lot about this recently and have been having some conversations around the general weaknesses of purely server-driven content negotiation (for many of the reasons you mentioned). Rather than noting my thoughts in comments, I’m going to just do a new post describing where my thinking is at the moment regarding conneg and what that means for versioning/metadata/linking. Much of this thinking has been coming out of the NuGet API redesign effort, so should be able to provide some concrete examples behind the concepts.
Howard, this was a fantastic read and a nice compliment to your pluralsight video as well. Where things break down for me (and others that I know) is when we make the assumption that our consumers will be able to specify content types or version numbers in a request for a resource. Maybe you can chime in here. Drawing from your example, let’s say that your uri is as follows:
Performing a GET, with accept: text/json against the above api returns a data representation of Grace. So you might get back.
Performing a GET, with accept: image/jpg against the above api, will return an image of Grace.
So me as a consumer, might write a web page to consume that api. Assuming that I want the image representation of Grace, I would write the following HTML:
Where this breaks down is that I cannot specify the accept type in the image tag, so how can I be sure that the server will default my request to image/jpg?
Further, If i wanted version 2, I cannot specify that either. So I feel like there might actually be some validity in using a version based uri scheme for these types of scenarios.
Many times, consumers of your apis will just be the average browser user, who doesn’t have the first clue about HTML, so the only way for these users to access version 2 of grace’s image would be via api/v2/children/grace/image. Certainly they’re not going to figure out a way to specify a version type or accept type in the header.
So unless I’m missing something, it just feels like we have to consider our audience when designing our restful services.
I would REALLY love your thoughts on this. I think where RESTFul design practices break down for people is around the disparity in which they are consumed.
“but it seems an illogical default” & # 8211; it’s not an illogical default… it would be more illogical to return version 2.1 definitions.
Just got pointed to this from Mike Wasson’s blog post. A couple of queries on implementing this.
You’d presumably want to accept some standard mime type as well? Isn’t this slightly manipulating content negotiation more than the intended usage – your response isn’t going to be a different content type as such. If going this route, wouldn’t your preferred accept type be Content-Type: text/html;apiVersion=1.1?
In your closing you stipulate (I’m paraphrasing) that if you’re changing the meaning of the resource, change the URL. In most mature production systems this would mean having both versioning strategies in play – content type negotiation and URLs. Isn’t REST, and the API strategy in general, supposed to promote inferable APIs which is a little tricky if I have to reference /api/v2/person and supply a specific content type? i. e. I’d have to provide both the v2 in the URL and a v2 in the content type otherwise the next ‘breaking change to the representation’ (bullet point 2 in closing) that is made by the api would also break my client code as the logical default content-type, if using text/html, would be the ‘latest’ version of the api? Obviously this could be fixed by not deafulting to the latest version of the api but it seems an illogical default; my point is that mixing the two seems somewhat fraught.
Obrigado! I believe I have a lot to learn, but you provided a bunch of useful information.
so there are a few questions in here, some related to versioning, some related to HTTP, and some related to REST in general. I’ll try to address a couple points here, but very strong urge you to read the Fielding dissertation for a more comprehensive treatment of REST, as well as other architectural styles that REST compares/contrasts with.
“When you are using the accept header for versioning, aren’t you essentially extending the URI to include the header?”
Na verdade não. At the meta level, in this example the accept header (though content negotiation can be based on any bit of metadata, not just the accept header) is used to make a statement about the version of the *representation* whereas the URL identifies a resource. Practically, this means that that set of URLs that make up a service’s resource model can remain stable across multiple versions of changes in the representation (including the addition of new capabilities).
One thing that used to keep me from fully getting the concept of versioning representations like this was when I used to equate representation with entity (e. g. I have a bug representation, a customer representation, etc..). When I redefined representation (and the representation design) to include everything that could show up in the representation (Mike Amundsen calls these ‘state blocks’ in his book shop. oreilly/product/0636920020530.do), versioning the representation made a lot more sense wrt both the value in doing it and a practical approach.
“you are now requiring the REST server to now read the HTTP headers, instead of just the request”
At the risk of sounding like I’m nitpicking, there is no such thing as a REST server – REST is just a style of distributed systems architecture. I am using an HTTP server, though, and am quite confident that it knows how to read and process HTTP headers.
“it should be implemented over a protocol that can transfer a meaningful representational state, such as HTTP”
Based on my reading waaayyy too much into this statement, I think you’re conflating a couple different things together (specifically, the self-describing message constraint of REST with the definition of representation state with the definition of HTTP) – the dissertation should help here better than my comments on your comments.
‘Technically, this could be achieved in the HTTP request: ” HTTP/1.1″‘
I don’t see how you could realize the self-describing message constraint (or the stateless constraint or the caching constraint, etc.) of REST with a protocol version, resource identifier, and one piece of control flow data. The data elements in the RESTful style include resource metadata, representation metadata, and control data – which HTTP implements as headers. As I remember it, Fielding’s only issue with this design was that all of the different types of metadata were reduced to the same flat list of headers in HTTP, making it less apparent what header was what type of metadata. One comment he did make that validates something you mentioned was that he agreed that cookies were generally contrary to the principles of the RESTful style, as they reduced the visibility (e. g. self-describing messages) and in practice, tend to be used in ways that also undermine the stateless constraint.
Past that, I’m not sure that we’ve gotten carried away with using a major component of a protocol that was meant to be used as an application protocol.
Hope this long-winded response was helpful – thanks for commenting!
When you are using the accept header for versioning, aren’t you essentially extending the URI to include the header? I don’t see much of a difference between GET /bugs/v2 and GET /bugs with a header with v2, except you are now requiring the REST server to now read the HTTP headers, instead of just the request.
At the University of Minnesota, we have recently been discussing REST architecture in the Software Engineering program. I haven’t read Roy Fielding’s dissertation on REST yet, but I think I should. In my understanding of REST, it should be implemented over a protocol that can transfer a meaningful representational state, such as HTTP. Technically, this could be achieved in the HTTP request: ” HTTP/1.1″. But I think we took it too far by using HTTP headers (authentication, cookies, and in your example accept).
What do you think about this?
2 separate issues (‘things’ getting unwieldy and pretending that there is no network) – sorry if the way the post was worded made it seem like I was conflating the two.
Anyways, here’s the scenario I was thinking of wrt the ‘unwieldy’ comment. Because the unit of versioning is the service in the Web services world, then most of the changes that you make to to the service (e. g. a service operation) will require you to increment the service version. Coupled with the fact that services tend to be more coarsely grained means that you can very quickly end up with myhost/api/v2*10^8 (ok, that may be a *little* dramatic). Having this many versions is confusing to clients, but more importantly can become a pain to maintain on the server (e. g. how many interfaces does your service class end up implementing over time?).
There are plenty of problems with frameworks that enable you to pretend that there is no network (e. g. work with services as if they were local objects via proxies) – but in this post, I was thinking of the difficulty that can happen when the service versions change and clients need to rev to support the new service version – even if the client in question only needs the functionality of 1 operation/resource. In this case, even though the client is only using one new capability (or 1 bug fix), the client needs to test for everything that has changed in the new version.
Howard, I’m a bit confused about “It doesn’t take much imagination to see how unwieldy this can get after even a few iterations – especially when you’re clients are interacting with the service by generating strongly typed proxies and then pretending that there is no network”
I don’t get your point here…. “pretending there is no network”. Proxies will keep on hitting nuget/api/v1/ and everything is fine.. or what do I miss?
Комментарии
Отправить комментарий