Mantendo versões em evolução de estruturas e classes de interoperabilidade

5

Um aplicativo C # .NET fala com um componente externo chamando uma API conhecida e organizando estruturas de interoperabilidade a partir da resposta do componente.

Isso já está implementado e funcionando bem. No entanto, uma vez que o versionamento entra na equação, as coisas ficam um pouco mais complicadas: como o componente evoluirá com o tempo, exigindo que as estruturas de interoperabilidade sejam mantidas em sincronia. O aplicativo ainda deve poder falar com componentes em versões mais antigas, de modo que ele tenha que escolher as estruturas de interoperabilidade corretas em tempo de execução para uma determinada versão do componente.

Eu tenho jogado esse problema na minha cabeça por alguns dias e não tenho nada com o que eu esteja particularmente feliz. O Google também não ajudou muito, então pensei em tentar escrever isso e publicá-lo aqui, em um esforço para obter algum feedback. Isso é o que eu tenho até agora.

Empacota todas as versões das estruturas em uma biblioteca, tem um registro e decide em tempo de execução quais estruturas usar

Aqui, cada nova versão das estruturas é compilada na nova versão do aplicativo .NET, e algum tipo de registro é responsável por mapear as versões da API para interoperar as versões da estrutura na biblioteca.

A nomenclatura de classe / estrutura se torna um problema, pois Class1 agora teria que ser chamado de Class1_v1_1 ou alguma outra forma de diferenciar com base no nome (ou isso, ou usar o mesmo nome e colocá-los em um namespace separado ).

Carregar versões das estruturas de interoperabilidade de uma DLL com versão

Aqui, cada versão das estruturas de interoperabilidade seria compilada em uma DLL individual e carregada dinamicamente com base na versão da API. Um mapeamento ainda precisa ser criado entre a versão da API e o nome da DLL.

O problema de nomenclatura desaparece, mas certamente há muito mais partes móveis para implementação e, consequentemente, coisas que podem dar errado em tempo de execução.

Como eu disse, eu não estou feliz com nenhum deles e sinto que há uma solução muito óbvia / mais limpa / elegante que está me escapando. Eu não posso imaginar que eu sou a primeira pessoa que teve que suportar a mudança de versões de um componente dependente, então existe algum padrão conhecido que pode fornecer alguma orientação para cenários como este?

Agradecemos antecipadamente por qualquer feedback & / ou possíveis alternativas!

    
por user5877732 03.08.2017 / 13:09
fonte

2 respostas

1

A prioridade número um deve ser minimizar as alterações na API de interoperabilidade, se você tiver algum controle sobre a "API conhecida". Um bom design de API minimiza as alterações ao longo do tempo.

Ambas as soluções funcionariam e a solução de dll com versão pode ser mais limpa, mas mais volumosa, já que você provavelmente tem muito da API que não muda entre as versões. Você terá que gerenciar a reutilização de código de alguma forma, talvez usando uma biblioteca comum.

Eu detectaria a versão usando uma chamada para a API conhecida, se você puder, em vez de usar um registro. Dessa forma, você não é forçado a usar a mesma versão em toda a máquina.

    
por 03.08.2017 / 19:25
fonte
0

Se possível, a introdução de um número de versão em cada estrutura é uma abordagem, que é particularmente popular com APIs REST e formatos de arquivo (texto simples e binário). Seu aplicativo, então, tem a capacidade de atualizar cada versão para a próxima. Isso significa que você só precisa se preocupar com um caminho de atualização, e pode apenas encadear atualizações conforme necessário. Se desejar, é possível adicionar outros caminhos de atualização para acelerar e até mesmo fazer downgrade de caminhos (geralmente são visíveis em programas em que várias versões são mantidas ao mesmo tempo e geralmente há um custo que impede que muitos clientes façam upgrade imediatamente).

Então, por exemplo, talvez você tenha uma Employee struct. Talvez a versão um seja realmente simples:

{
    '_version': '1.0'
    'first_name': 'Amelia',
    'last_name': 'Bedelia',
    'employee_id': 123,
    'salary': 65000
}

Mas depois, suponha que realmente queremos combinar esse campo de nome. Vamos apenas fazer essa mudança porque, presumivelmente, há uma boa razão comercial para isso! Temos uma nova versão para nossa estrutura:

{
    '_version': '1.1'
    'name': 'Amelia Bedelia',
    'employee_id': 123,
    'salary': 65000
}

Agora, nós poderíamos ter apenas nossa ramificação da base de código no número da versão, mas é muito mais à prova do futuro e geralmente mais limpa para simplesmente atualizar a estrutura antiga para algo compatível com a nova. Isso pode envolver o preenchimento de padrões razoáveis, ler informações adicionais de outros lugares para preencher espaços em branco, solicitar aos usuários essas informações ou o que for. A ideia, no entanto, é garantir que sua base de código só precise lidar com a versão mais recente da estrutura.

Portanto, teríamos alguma função de atualização que tenha algum ramo como:

if employee['_version'] == '1.0':
    # Let's just make an assumption about names and combine it blindly
    employee = {
        '_version': '1.1',
        'name': employee['first_name'] + ' ' + employee['last_name'],
        'employee_id': employee['employee_id'],
        'salary': employee['salary']
    }
# And so on for other version upgrades, returning the final object
# at the end.

E então continuaríamos fazendo isso até que tenhamos a versão atual (que poderia então ser convertida em um tipo mais strong). Esse padrão também funciona bem para atualizações de banco de dados (é como o manuseio recomendado do Android para seus bancos de dados SQLite funciona).

Agora, esta resposta tem sido de alto nível até agora. Mas não é exclusivo para onde a estrutura de dados pode ser representada como um dicionário genérico (embora seja certamente uma estrutura de dados versátil e adaptável para troca de informações entre os limites do sistema). Se os dados puderem ser armazenados em um formato binário bruto e sempre tivermos a versão em uma determinada posição, poderemos escolher facilmente qual estrutura de baixo nível será usada para interpretar os dados (e depois aplicar a atualização a partir deles). Nesse caso, ainda precisaríamos armazenar essas estruturas em algum lugar (mas o valor vem de não ter muitos caminhos de código para essas diferentes versões de estruturas).

    
por 08.08.2017 / 21:54
fonte