APIs de versão

9

Suponha que você tenha um grande projeto suportado por uma base de API. O projeto também envia uma API pública que os usuários finais (ish) podem usar.

Às vezes, você precisa fazer alterações na base da API que suporta seu projeto. Por exemplo, você precisa adicionar um recurso que precise de uma alteração na API, um novo método ou exija a alteração de um dos objetos ou o formato de um desses objetos, passados ​​para ou a partir da API.

Supondo que você também esteja usando esses objetos em sua API pública, os objetos públicos também serão alterados sempre que você fizer isso, o que é indesejável, pois seus clientes podem confiar nos objetos da API que permanecem idênticos para que seu código de análise funcione. (tosse clientes C ++ WSDL ...)

Portanto, uma solução potencial é a versão da API. Mas quando dizemos "version" a API, parece que isso também deve significar a versão dos objetos da API, além de fornecer chamadas de método duplicadas para cada assinatura de método alterada. Então, eu teria um objeto clr antigo e simples para cada versão da minha API, o que novamente parece indesejável. E mesmo que eu faça isso, certamente não construirei cada objeto do zero, pois isso acabaria com grandes quantidades de código duplicado. Em vez disso, é provável que a API estenda os objetos particulares que estamos usando para a API base, mas depois enfrentamos o mesmo problema, porque propriedades adicionadas também estariam disponíveis na API pública quando não deveriam.

Então, que sanidade geralmente é aplicada a essa situação? Conheço muitos serviços públicos, como o Git for Windows, que mantém uma API com versão, mas estou tendo problemas para imaginar uma arquitetura que suporte isso sem grandes quantidades de código duplicado que abrangem os vários métodos e objetos de entrada / saída.

Estou ciente de que processos como o controle de versão semântico tentam melhorar a integridade quando ocorrem quebras de API públicas. O problema é mais: parece que muitas ou a maioria das alterações exigem quebra da API pública se os objetos não estiverem mais separados, mas não vejo uma boa maneira de fazer isso sem duplicar o código.

Caso
fonte
11
I don't see a good way to do that without duplicating code- Sua nova API sempre pode chamar métodos em sua API antiga ou vice-versa.
Robert Harvey
2
O AutoMapper para o resgate, infelizmente sim - você precisa de versões distintas de cada contrato, não esqueça que todos os objetos referenciados pelo seu contrato fazem parte desse contrato. O resultado é que sua implementação real deve ter sua própria versão única dos modelos e você precisa transformar a versão única nas várias versões do contrato. O AutoMapper pode ajudá-lo aqui, além de tornar os modelos internos mais inteligentes que os modelos de contrato. Na ausência do AutoMapper, usei métodos de extensão para criar traduções simples entre modelos internos e modelos de contrato.
Jimmy Hoffa 10/10
Existe uma plataforma / contexto específico para isso? (ie DLL, uma API REST, etc.)
GrandmasterB
.NET, com MVC e Webforms ui, classes dll. Temos uma API de descanso e sabão.
Case

Respostas:

6

Ao manter uma API usada por terceiros, é inevitável que você precise fazer alterações. O nível de complexidade dependerá do tipo de mudança que está ocorrendo. Estes são os principais cenários que surgem:

  1. Nova funcionalidade adicionada à API existente
  2. Funcionalidade antiga preterida na API
  3. Funcionalidade existente na API mudando de alguma maneira

Nova funcionalidade adicionando à API existente

Este é o cenário mais fácil de suportar. Adicionar novos métodos a uma API não deve exigir nenhuma alteração nos clientes existentes. É seguro implantar para os clientes que precisam da nova funcionalidade, pois não possui atualizações para nenhum cliente existente.

Funcionalidade antiga preterida na API

Nesse cenário, você deve comunicar aos consumidores existentes da sua API que a funcionalidade não será suportada a longo prazo. Até que você abandone o suporte para a funcionalidade antiga (ou até que todos os clientes tenham atualizado para a nova funcionalidade), você deve manter a funcionalidade antiga e nova da API ao mesmo tempo. Se for uma biblioteca fornecida, a maioria dos idiomas pode marcar métodos antigos como obsoletos / obsoletos. Se for de algum tipo um serviço de terceiros, geralmente é melhor ter pontos de extremidade diferentes para a funcionalidade antiga / nova.

Funcionalidade existente na API mudando de alguma maneira

Este cenário dependerá do tipo de alteração. Se os parâmetros de entrada não precisarem mais ser usados, basta atualizar o serviço / biblioteca para ignorar os dados extras agora. Em uma biblioteca, o método sobrecarregado chama internamente o novo método que requer menos parâmetros. Em um serviço hospedado, o terminal ignora dados extras e ele pode atender a ambos os tipos de clientes e executar a mesma lógica.

Se a funcionalidade existente precisar adicionar novos elementos necessários, você deverá ter dois pontos de extremidade / métodos para seu serviço / biblioteca. Até a atualização dos clientes, você precisa oferecer suporte às duas versões.

outros pensamentos

Rather, the API is likely to extend the private objects we are using for our base API, but then we run into the same problem because added properties would also be available in the public API when they are not supposed to be.

Não exponha objetos particulares internos através da sua biblioteca / serviço. Crie seus próprios tipos e mapeie a implementação interna. Isso permitirá que você faça alterações internas e minimize a quantidade de atualizações que os clientes externos precisam fazer.

The problem is more that it seems like many or most changes require breaking the public API if the objects aren't more separated, but I don't see a good way to do that without duplicating code.

A API, seja um serviço ou uma biblioteca, precisa estar estável no ponto de integração com os clientes. Quanto mais tempo você gasta para identificar quais devem ser as entradas e saídas e mantê-las como entidades separadas, você economizará muitas dores de cabeça no caminho. Faça com que a API contrate sua própria entidade separada e mapeie para as classes que fornecem o trabalho real. O tempo economizado quando as implementações internas mudam deve compensar o tempo extra necessário para definir a interface extra.

Não veja esta etapa como "código duplicado". Embora semelhantes, são entidades separadas que valem a pena ser criadas. Embora as alterações externas da API quase sempre exijam uma alteração correspondente à implementação interna, as alterações internas da implementação nem sempre devem alterar a API externa.

Exemplo

Suponha que você esteja fornecendo uma solução de processamento de pagamento. Você está usando o PaymentProviderA para fazer transações com cartão de crédito. Mais tarde, você obtém uma taxa melhor através do processador de pagamento do PaymentProviderB. Se sua API expôs os campos Cartão de crédito / Endereço de cobrança do seu tipo, em vez da representação de PaymentProviderA, a alteração da API é 0, pois a interface permaneceu a mesma (esperamos que seja, de qualquer maneira, se PaymentProviderB exigir dados que não foram exigidos por PaymentProviderA, você precisará escolher entre suporte para ambos ou mantenha a pior taxa com PaymentProviderA).

Phil Patterson
fonte
Obrigado por esta resposta muito detalhada. Você conhece algum exemplo de projetos de código aberto que eu possa ler para saber como os projetos existentes fizeram isso? Eu gostaria de ver alguns exemplos concretos de como eles organizaram código que lhes permite fazer isso sem apenas copiar seus vários códigos de construção POCO nos vários objetos de versão, pois se você chamar métodos compartilhados, precisará dividi-los do método compartilhado para poder editar esse objeto para o objeto com versão.
Case
11
Não estou ciente de nenhum bom exemplo. Se tiver algum tempo neste fim de semana, poderia criar um aplicativo de teste para lançar no GitHub para mostrar como algumas dessas coisas podem ser implementadas.
Phil Patterson,
11
A parte complicada é que, de alto nível, existem muitas maneiras diferentes de tentar minimizar o acoplamento entre clientes e servidores. O WCF possui uma interface chamada IExtensibleDataObject, que permite passar dados que não estão no contrato do cliente e enviá-los pela conexão ao servidor. O Google criou o Protobuf para comunicação entre sistemas (existem implementações de código aberto para .NET, Java etc.). Além disso, existem muitos sistemas baseados em mensagens que também podem funcionar (supondo que seu processo possa ser executado de forma assíncrona).
Phil Patterson
Pode não ser uma má idéia mostrar um exemplo específico da duplicação de código que você está tentando remover (como uma nova pergunta de estouro de pilha) e ver quais soluções a comunidade apresenta. É difícil responder à pergunta em termos gerais; portanto, um cenário específico pode ser melhor.
Phil Patterson