Estratégia de versão da rede


Estratégia de versão
Obter através da App Store Leia esta publicação em nosso aplicativo!
Qual é a sua estratégia de versão do aplicativo? [duplicado]
Esta questão já tem uma resposta aqui:
Eu estaria interessado em obter as opiniões da comunidade SO sobre a melhor estratégia de versão de aplicativos.
Como você acompanha o número da versão do seu aplicativo? Você tem uma definição formal do que representa cada número / personagem dessa versão?
O que significam os diferentes números / strings na versão do aplicativo para o seu aplicativo?
Você usa qualquer sistema de atualização automatizado em suas aplicações (por exemplo, algo como Sparkle) e como ele tem sido bom para você?
Você tem um procedimento de atualização separado para testadores beta ou testadores de pré-lançamento do seu aplicativo?
marcado como duplicado pelo mosquito, MichaelT, Kilian Foth, GlenH7, Rein Henrichs 29 de abril 13 às 2:42.
Esta pergunta foi feita antes e já tem uma resposta. Se essas respostas não respondem totalmente a sua pergunta, faça uma nova pergunta.
migrou do stackoverflow 18 de maio às 11h48.
Esta questão veio do nosso site para programadores profissionais e entusiasta.
Como você acompanha o número da versão do seu aplicativo? Você tem uma definição formal do que representa cada número / personagem dessa versão?
O que os diferentes números / strings da versão do aplicativo significam para seu aplicativo?
Eu uso o seguinte:
Major - A versão principal é uma versão definitiva do produto. Aumentou quando há mudanças significativas na funcionalidade.
Menor - A versão menor é incrementada quando apenas foram adicionados novos recursos ou grandes correções de bugs.
Upgrade / Patch - Upgrade refere-se à substituição de um produto por uma versão mais recente do product. It é incrementado somente quando a atualização é fornecida na versão principal designada. A versão do programa começa com 0 e é incrementada somente quando o erro foi resolvido.
Build No - Build Number é incrementado quando a nova compilação é criada.
Você usa qualquer sistema de atualização automatizado em suas aplicações (por exemplo, algo como Sparkle) e como ele tem sido bom para você?
Usamos a ferramenta de construção que constrói automaticamente o aplicativo à noite, o que chamamos de construção noturna e isso aumenta o número de compilação sempre que uma construção é criada.
Você tem um procedimento de atualização separado para testadores beta ou testadores de pré-lançamento do seu aplicativo?
Não. Testes de testes durante a noite, a cada manhã, a que chamamos BAT (Build Acceptance Test, teste de aceitação de construção) e verificações noturnas.
Permitam-me que note primeiro que parece não haver acordo sobre a "melhor" estratégia. Só posso compartilhar minha experiência em um projeto atual.
A versão do sistema é definida manualmente em uma propriedade de compilação. Acontece quando a equipe concorda em uma nova versão. Como versão de versão adicional, usamos o número de compilação que é gerado automaticamente pela construção do CI.
Seguimos vagamente o esquema de nomeação do Ubuntu YY. MM. version. patch_buildNumber, pois descobrimos que o controle de versão Major. Minor sujava as expectativas dos clientes;)
Não há atualizações automáticas, pois o aplicativo deve ser lançado por adminstrators.
Os lançamentos de teste são mais freqüentes do que as versões da GA, mas isso deve ser tudo.
Testei muitos sistemas de versões e agora estou muito feliz com este:
O Major é configurado manualmente e se refere a uma versão que inclui importantes melhorias. Menor também é configurado manualmente e se refere a uma versão de atualização / manutenção, incluindo melhorias menores e amp; correção A revisão é gerada automaticamente e se refere a uma revisão exata no repositório.
O último nos permite ser muito flexíveis com a versão. Podemos enviar múltiplas versões para vários clientes e ainda ser capazes de depurar & amp; conserte com facilidade obtendo a versão específica dos repos, então combine novamente com o tronco.
A construção, embalagem e amp; A publicação é totalmente automatizada. A única ação manual é quando movemos o último pacote para o servidor de produção por FTP. Queremos manter o controle sobre isso para garantir que não entregamos lixo na produção. Ele é um estágio preliminar em que os adotadores iniciais podem ler as notas da versão e depois decidir baixar e usar a versão. Os clientes que enfrentam erros específicos podem obter uma versão fixa muito rápido usando uma dessas versões.
Eu uso o Versioning Semântico para minhas bibliotecas de código aberto e acho muito mais fácil trabalhar com outras bibliotecas que também o fazem. Ele fornece uma base comum para entender o que uma mudança de versão pode significar. A biblioteca ainda está em versão beta? É um lançamento apenas para correções de bugs? Haverá mudanças de API quebradas?
É basicamente uma codificação das melhores práticas de versão já utilizadas pela maioria dos projetos de código aberto.

Estratégia de versão
Obter através da App Store Leia esta publicação em nosso aplicativo!
Estratégias para atualização ou versão de serviços da Web?
Estou interessado em ouvir as melhores práticas sobre como as diferentes versões dos serviços da Web são tratadas.
Para esclarecer, se você tiver alguns métodos da Web expostos como um serviço da Web, então você deseja adicionar uma característica / funcionalidade e, assim, alterar a assinatura dessas chamadas de método, como você lida com isso de uma maneira que não quebra tudo de seus clientes que atualmente ligam para o serviço?
Você implanta o serviço em um URL diferente?
Você coloca uma versão no próprio nome do método (MyMethod, MyMethodv2 etc. - ugh ..)
Você passa uma versão como parte da chamada do método junto com uma lista de parâmetros?
Alguém sabe como o Google ou o Amazon lidam com esse cenário com sua extensa biblioteca de serviços da Web?
EDIT: Até agora, encontrei algumas informações boas neste artigo da Oracle. Também esta entrada de blog em alguns detalhes do Java foi útil. Ainda estou curioso para ver algumas das outras abordagens.
A maneira típica de versionar um serviço da Web é ter clientes especificando a versão desejada. Você pode permitir restrições simples, como "> 2.0", "& lt; 1.5", ou "= 1.1". Naturalmente, você deseja minimizar o número de versões suportadas para sua própria sanidade. Se um cliente não especificar uma versão, você assume o mais recente.
As técnicas para fornecer a versão variam. Alguns defendem o uso do URL, outros encorajam cabeçalhos, alguns podem incluí-lo como um parâmetro da chamada api. Quase nenhum mudaria o nome do método, no entanto. Isso é equivalente ao "pacote" ou "namespace" versionando o link OSGi. Isso tornará a atualização muito difícil e impedirá as pessoas de atualizar mais do que quaisquer alterações ao serviço real.
Também depende de como você acessa seus serviços da web. Se você estiver usando REST, manter os cabeçalhos limpos e usando a URL faz mais sentido (e seria trivial inseri-la como um parâmetro de consulta, se necessário). Se você estiver usando SOAP / XMLRPC / o que for, o RPC, então, colocá-lo no URL geralmente é bom.
Como o cliente especifica a versão geralmente é bastante fácil. O que é mais complicado é como você executa todas as versões simultaneamente. A maioria das linguagens não tem como carregar várias versões da mesma biblioteca / módulo / classe / função no mesmo ambiente de tempo de execução (seja uma VM, processo ou o que você tem). O link OSGi fornecido é a solução de Java para permitir isso.
Na prática, a OSGi será superada para a maioria das situações. Normalmente, é mais fácil enviar solicitações obsoletas para outro servidor ou processo.
A melhor maneira de "versão" de seus serviços, no entanto, é criar extensibilidade e flexibilidade para eles, de modo que permaneçam compatíveis e compatíveis. Isso não significa que todas as versões devem ser compatíveis entre si, mas as versões consecutivas devem ser compatíveis entre si.
Posso dizer-lhe que a solução de criar doAPIFunction, doAPIFunctionV2 e doAPIFunctionV3, etc produziu apenas dores de cabeça no local onde trabalho. Adicione a isso a falta de nomes de funções claramente descritivos significa todo tipo de loucura.
Você quer nomes claros da função da API e, se uma api estiver mudando, o objetivo seria tentar fazê-lo de forma compatível com versões anteriores. Eu sugeriria o controle de versão de seus pontos de entrada para que cada ponto de entrada suporte uma API estável e doFunction no exemplo / api-1.0 / pode ser diferente do exemplo / api-2.0 se existisse uma boa razão para mudar a semântica.
Não tenho certeza se eu entendi sua pergunta corretamente. Mas meus 2 centavos. Se a assinatura do método muda como outro novo parâmetro, por que não pode ser feito opcional? Mas se um tipo de dados de parâmetro existente for alterado, não será aplicável.
Vote para isso se está errado. :)
Ele costumava ser mais simples quando você poderia ter o mesmo nome do método de serviço na web, e diferentes parâmetros, há anos. :)
Uma vez que você escreve um serviço na web, você está fazendo um contrato com os clientes que é isso que você apoiará.
Para métodos mais antigos, sugiro iniciar sessão para ver se eles estão sendo usados ​​e por quem, para ver se você pode fazê-los atualizar.
Além disso, é melhor você simplesmente escrever um novo método, então você pode ter vários nomes de funções similares que diferem por uma versão.
O problema com a passagem de um número de versão é que você deve garantir que os clientes sempre passem num número válido, o que, se você gerar os talões, funcionará, mas, se não o fizer, isso é muito frágil.
Colocar um número de versão no nome parece funcionar melhor para mim, pois torna óbvio o que é antigo e você pode usar o AOP para registrar mais facilmente versões antigas dos métodos de serviço na web.

Em ALM com TFS / VSTS.
blogando sobre Gerenciamento do ciclo de vida do aplicativo com TFS / VSTS.
Estratégia de versionamento global & # 8211; AssemblyInformationalVersion.
Já ouvi falar de um terceiro atributo de versão (opcional) nos arquivos AssemblyInfo: AssemblyInformationalVersion. Não? Por favor leia!
Sem uma estratégia de numeração de versão metódica (assembly), a capacidade de determinar quais alterações foram incluídas em qual versão foi perdida. Na minha opinião, você sempre precisa saber exatamente quais arquivos de origem foram instalados em qual compilação e qual versão do software está atualmente implantada em um ambiente específico. Um sistema de numeração de versão aleatória cria confusão e logo ou mais tarde causará riscos de implantação. Será um pesadelo buscar os arquivos de origem exatos para reproduzir um bug da produção.
Todo o controle de versão de assembléias que usam o tempo de execução de linguagem comum é feito no nível de montagem. A versão específica de uma montagem e as versões de montagens dependentes são registradas no manifesto de montagem # 8217 ;. A política de versão padrão para o tempo de execução é que os aplicativos são executados apenas com as versões em que foram criados e testados, a menos que sejam substituídos pela política de versão explícita nos arquivos de configuração.
Cada projeto possui um arquivo AssemblyInfo que contém um atributo AssemblyVersion e um atributo AssemblyFileVersion.
AssemblyVersion: este é o número de versão usado pela estrutura durante a compilação e no tempo de execução para localizar, vincular e carregar as montagens. Quando você adiciona uma referência a qualquer montagem em seu projeto, esse é o número da versão que é incorporado. Em tempo de execução, o CLR procura a montagem com este número de versão para carregar. Mas lembre-se que esta versão é usada juntamente com nome, chave pública chave e informações culturais somente se as assembléias tiverem o nome forte assinado. Se os assemblies não tiverem um nome forte assinado, somente os nomes dos arquivos são usados ​​para carregar. AssemblyFileVersion: Este é o número de versão dado a um arquivo como no sistema de arquivos. É exibido pelo Windows Explorer. Nunca é usado por framework ou tempo de execução para referência.
Mas e quanto a essa diferença entre AssemblyVersion e AssemblyFileVersion? Muitas vezes, vejo que a mesma versão é aplicada a ambos os atributos e # 8230; mas por que esses dois atributos (diferentes) são fornecidos pelo Framework? O AssemblyVersion deve ser a versão pública de um aplicativo de software completo, enquanto o AssemblyFileVersion é mais a versão de um componente específico que pode ser apenas uma pequena parte do aplicativo inteiro. O AssemblyFileVersion é o melhor lugar para colocar informações de versão de compilação extra que podem ser importantes para corrigir componentes individuais de um aplicativo de software.
Por favor, siga as recomendações de versão semântica para ditar como o AssemblyVersion deve ser atribuído e incrementado. Para o AssemblyFileVersion, tendem a incluir informações de compilação específicas. Muitas vezes, você precisará criar (e testar) uma série de vezes uma versão SemVer específica do seu software.
Por exemplo: a versão 1 de um aplicativo de software poderia ter o AssemblyVersion configurado para 1.0.0 (todos os componentes), enquanto o AssemblyFileVersion dos componentes individuais poderia ser configurado para 1.0.15234.2 que se refere a um número de compilação exclusivo do sistema de compilação e é vinculada a uma data específica e a uma revisão: & # 8220; 15 & # 8221; = ano de 2015; & # 8220; 234 & # 8221; = número do dia em 2015; & # 8220; 2 & # 8221; = segunda compilação processada nesse dia. Isso também permite, posteriormente, corrigir componentes individuais em produção com um AssemblyVersion (1.0.0) semelhante, mas um AssemblyFileVersion diferente (1.0.15235.1).
Então, vamos tentar aplicar isso a um projeto de teste no Visual Studio e ver os detalhes da montagem após a construção do projeto & # 8230;
Agora você deve estar confuso! Por que a versão do produto exibe AssemblyFileVersion e onde o AssemblyVersion? O problema aqui é que um novo projeto do Visual Studio não inclui um atributo de terceira versão AssemblyInformationalVersion que se destina a representar a versão pública de todo o aplicativo de software. Observe que o CLR não se preocupa com esse terceiro atributo de versão (opcional). Em suma, as mesmas regras de versão semântica da AssemblyVersion devem ser aplicadas ao AssemblyInformationalVersion.
Ah! Isso parece muito melhor, certo? Agora também é fácil extrair esses metadados de seus assemblies implantados e essas informações podem ser bem listadas na caixa sobre do seu software. O único problema com esta abordagem é que o AssemblyFileVersion não inclui o & # 8220; patch & # 8221; número (versão semântica) do AssemblyVersion, mas isso pode ser ignorado com o fato de que o AssemblyFileVersion será exclusivo e pode ser vinculado a uma execução de compilação exclusiva no sistema de compilação. Esta maneira de trabalhar é minha interpretação pessoal de como o controle de versão pode ser aplicado corretamente em aplicativos de software complexos e não reflete as diretrizes oficiais da Microsoft. Meu objetivo aqui é fazer os desenvolvedores de software conscientes dos riscos potenciais de não ter uma estratégia de versão de versão clara.
Agora, esqueça manualmente a configuração das informações da versão nos arquivos AssemblyInfo e nunca libere o software de uma compilação Visual Studio local. Em um processo de compilação simplificado, gerar números exclusivos de compilação e versão são coordenados de forma centralizada. Para uma solução de problemas e rastreabilidade eficazes, é imprescindível que os assemblies gerados sejam carimbados com um identificador exclusivo que possa ser facilmente rastreado até um número de compilação do sistema.
Em uma próxima publicação, falo sobre como você pode alcançar essa estratégia global de versão com o novo sistema de compilação no TFS 2015.
Pós-navegação.
Deixe uma resposta Cancelar resposta.
O Real Semver permite uma tag de pré-lançamento como esta: 1.2.4-beta04 & # 8221 ;. Semver também permite metadados como número de compilação, data, commit ou changeset como este & # 8220; 1.0.0 + 20130313144700 & # 8221 ;.
AssemblyInformationalVersion é o único campo que permite qualquer outra coisa que a notação de versão clássica, como 1.2.3.4. AssemblyVersion e AssemblyFileVersion só permitem a notação de versão estrita. E todas as partes estão limitadas a 65536. Eu acredito (porque por que precisamos de estranha notação para datas, certo?).
Você sugere a configuração AssemblyInformationalVersion e AssemblyVersion para a mesma versão, mas depois perdemos essa poderosa característica / idéia. E tudo o que está à esquerda é colocar uma certa data de representação do número de compilação no AssemblyFileVersion (desde que ele seja na forma de 1.2.3.4)
Talvez AssemblyInformationalVersion deve começar com AssemblyVersion, mas permitir que a tag e os metadados sejam anexados?
Ao empacotar um nupkg, a AssemblyInformationalVersion será usada. O que é ótimo, porque dessa forma você pode criar pacotes pré-lançamento. Mas pode não ser fácil configurar tags de pré-lançamento para suas compilações automatizadas.
O mínimo que podemos dizer, é uma bagunça, é confuso e a estrutura não foi construída com tudo isso em mente. Para piorar as coisas, temos um trabalho real a fazer em vez de tentar descobrir esses detalhes fúteis.
A informação de montagem global é uma boa ideia. Aliás, o Assembly Versioner e o Info Updater podem ajudar as versões de projeto / montagem do gerente e outras informações para todos os projetos / montagens em uma UI única:
O Visual Smarter tem muito mais recursos do que os dois acima.
Compartilhar uma versão comum de montagem / arquivo de informações é uma boa idéia se todas as assembléias / projetos devem ter a mesma informação / versão. Caso contrário, o Assembly Versioner e Info Updater podem vir a resgatar no último minuto:
O Visual Smarter tem muito mais recursos do que os dois acima.

Estratégia de versão
Obter através da App Store Leia esta publicação em nosso aplicativo!
Estratégia de versão para aplicativos no Git.
Minha empresa simplesmente começou a usar git e estou lutando com o controle de versão. Estamos usando a versão semântica (major. minor. patch), que definimos com base nas mudanças feitas no produto. No entanto, eu gostaria de usar a quarta parte para indicar o commit git. Eu li artigos sobre como fazer isso, especialmente um usando o gancho pós-mesclagem, mas quanto mais eu entrou nisso, mais confuso me tornei, não na parte técnica, mas na parte conceitual.
Eu realmente gosto da idéia de poder usar o hash de commit git na versão, pois deixa muito claro o que é o software. No entanto, não posso saber isso até depois do commit, em que ponto, alterar a versão exigiria outro commit e o hash seria incorreto. Então, se eu conseguir um número de versão, vá para git, eu realmente estaria recebendo o commit antes do que estava em uso. O que eu quero é poder ir ao git, pegar a etiqueta e criá-la exatamente como está em uso.
Eu suspeito que a resposta reside no processo de compilação, na medida em que esse processo iria consultar o git, obter o hash, atualizar AssemblyInfo. cs e depois construir o produto.
Como sou muito novo para tudo isso, eu me pergunto se:
Estou perdendo algo conceitualmente? Existe uma maneira melhor do que a construção? Se o processo de compilação for o caminho a seguir, existem maneiras comprovadas de lidar com isso?
O hash (para git, a sha1) selar todo o conteúdo do commit. Se apenas uma coisa mudar no commit, o conteúdo de um arquivo ou um metadado do commit (data de commit, commit message, author.), O hash será diferente. Nesse caso, você entende que não pode obter o hash de commit, modificar o conteúdo de um arquivo e emendar o commit e esperar ter o mesmo hash para o commit! Então, se você quiser criar suas assembléias, você deve atualizar o arquivo AssemblyInfo. cs com o hash de confirmação em seu processo de compilação antes de compilar a solução.
Para aqueles curiosos, esta é a solução que acabei por usar.
Depois de confirmar que eu entendi como funcionou toda a coisa commit / hash, comecei a procurar mais o processo de compilação. Acontece que foi bem fácil. Este artigo me levou na direção certa. Aqui é o que eu fiz:

Estratégias de versão de software.
Um perfeccionista preso no mundo real.
Software Versioning pode ser uma dessas áreas onde você nunca sente como se você tivesse exatamente isso. Não existe uma orientação definitiva com uma solução que satisfaça todos. Principalmente equipes de software estão confusas sobre o assunto, ou estão escolhendo ignorá-lo. Este guia visa preencher a lacuna e oferecer uma visão prática de várias estratégias populares e trade-offs.
Algumas das técnicas serão voltadas para a Microsoft stack (Windows), pois é o que tenho mais experiência, mas os princípios se aplicam em geral. Linux, Node. js, Python & amp; Ruby também é levemente tocada.
Versões em todo lugar.
Estamos bastante acostumados com o termo "versão" hoje em dia. Mais comumente usado no mundo do software, vazou para a mídia e outras indústrias. As seqüências de filmes estão sendo versionadas - "Fast & Furious 7" (7 !?), os sapatos estão sendo versionados - "Air Jordan XX8" e, mais popular, os livros estão sendo versionados - "One Minute Manager, 1984 edition". Na verdade, olhando para os livros, as pessoas estão sendo controladas há bastante tempo - "Enciclopédia Britânica", desde 1768 !.
A premissa é simples - à medida que os produtos vivem e continuam a ser melhorados, os lançamentos mais recentes devem ser distinguidos dos anteriores. O nome do produto não muda, porque o mercado já se familiarizou com isso, então algo é anexado no final para indicar que ele é mais novo (ou diferente).
Enquanto o controle de versão existia muito antes da era digital, o software realmente empurrou o problema para a frente. Modificar e liberar uma nova cópia de software é um processo muito rápido, muitas vezes mais rápido do que mudar uma linha de produção industrial para produzir uma nova peça de roupa ou imprimir uma nova edição de livro. Assim, os ciclos de iteração do software são muito mais curtos, e um potencial para muitas edições simultâneas é muito maior.
Basta usar anos (ou mesmo meses), como em edições de livros, não é suficiente. Novas versões do software podem ser produzidas em poucos minutos. Além disso, o software tem um enorme aspecto paralelo - fluxos de software - onde várias versões principais podem existir e todas podem ser atualizadas continuamente ao mesmo tempo. Isso raramente acontece com seus sapatos. (Eu queria fazê-lo, às vezes eu simplesmente não quero atualizar para o modelo de catálogo deste ano, quero uma melhoria para o meu antigo par!)
Por que Versão?
Antes de mergulhar em como implementar o controle de versão, vamos parar e considerar o motivo pelo qual queremos fazê-lo em primeiro lugar! Afinal, se conhecemos as razões exatas de por que é útil, podemos avaliar melhor se as soluções propostas são adequadas.
Nós aludimos a isso na última seção, referindo-se ao chamado versão pública. Esta é a versão que é visivelmente visível e, principalmente, possui peso no mercado (ou seja, é mais provável que seja definido pelo departamento de marketing / vendas). "Windows 7", "iPhone 5S", "Office 2013" - são exemplos de uma versão pública.
A versão pública destina-se a ser simples e memorável, indicando aos clientes que é novo e brilhante (assumindo que as pessoas geralmente querem "novo e brilhante"). As pessoas não entendem "10.6.6527.14789" - mas recebem "2013" ou "5". Tem sido cada vez mais popular usar o ano de lançamento como o número da versão pública, pois transmite de forma simples e poderosa o status atualizado. Os fabricantes de automóveis estão fazendo isso há muito tempo.
A versão privada é o que estamos acostumados no mundo do software. Um selo interno que (espero) identifica de forma exclusiva um determinado software. O software, como um carro, pode ser feito de muitas partes. Seguindo a analogia do carro, a "versão privada" do carro é o número do chassi VIN. Os fabricantes liberam e mantêm catálogos maciços de peças, mapeando para "números de versão" do carro. Um mecânico pode encomendar uma peça exata que se ajustaria ao seu veículo.
Sem um "número de peça privada", você não seria capaz de atender seu software na natureza, pois não saberia o "formato" exato que um módulo de substituição deve ser para se adequar ao sistema geral. Imagine se você foi forçado a mudar seu carro inteiro quando uma luz da cauda quebrou.
Portanto, o número da versão particular é usado exatamente como um identificador de catálogo. Destina-se a ser usado quando solucionar problemas ou atender seu software. (Eu gosto da analogia "dogtag" de Jeff Attwood!) Ele deve mapear para uma descrição do que é essa peça de software - qual é a sua forma e função. E que melhor "descrição" do que o próprio código fonte original!
O uso essencialmente se resume a:
Identificação do código-fonte original para uma peça de software, para habilitar o patch incremental e para confirmar a operação defeituosa. Identificar se uma parte é "compatível" com outra ou se pode substituí-la.
Tudo isso é realizado com um número de versão privado. A versão pública é simplesmente um apelido de marketing e é mapeada para uma ou mais partes de software internas, cada uma com sua própria versão privada. (Assim como o Toyota Corolla 2011 contém um quadro ZRE142 e um conversor de torque 32000-12420)
Uso da versão.
No Windows, um conceito de número de versão é suportado por uma camada do sistema operacional. Os números de versão são incorporados em todos os arquivos executáveis ​​binários e podem ser vistos ao pairar sobre o EXE / DLL no Windows Explorer ou ao exibir Propriedades. De fato, qualquer arquivo que possa ter "recursos" pode ter uma versão, já que é armazenado no recurso VERSIONINFO.
Ele usa o formato comum em que todos nós somos usados ​​para: major. minor. build. revision (por exemplo, "1.2.360.0"). É importante notar que cada número é limitado a 16 bits e, portanto, não pode exceder 65535. Isso tem certas implicações sobre o que podemos representar com esses números.
Observe que um rótulo para esses números não é definido de forma estrita - são simples 4 números inteiros curtos. Os dois primeiros são referidos como maior e menor por unanimidade. Os dois últimos são onde vemos algumas variações, dependendo do esquema de versão.
Esta versão é usada de forma proeminente durante o processo de atualização do Windows, que utiliza a tecnologia Windows Installer (MSI) para atualizar várias partes do sistema. Essencialmente, o Windows Installer segue certas regras para determinar se a atualização que está instalando é mais nova que o que já está instalado. Se a versão for maior, então está ok para atualizar.
Naturalmente, esse conceito flui para o Framework, que foi construído em torno de muitos conceitos existentes do Windows. Nós temos a classe Version, que segue o paradigma 4 integer. Também podemos definir AssemblyVersionAttribute e AssemblyFileVersionAttribute, que especificam uma versão de montagem e um recurso de versão do Windows, respectivamente.
Em, a versão de montagem existe separadamente da versão baseada no Windows VERSIONINFO, que é o que você vê no Windows Explorer (ou propriedades do arquivo). Ele faz parte do nome forte do assembly e é usado exclusivamente pelo Framework ao resolver assemblies. A versão de duas montagens e a versão do arquivo do Windows - podem ser diferentes, mas, mais frequentemente, elas são as mesmas para evitar confusões.
usa a versão para rastreamento de dependência, ou seja, observando as versões de montagens a serem referenciadas, tornando assim óbvio quando uma atualização quebra a compatibilidade para aplicativos que dependem de uma determinada biblioteca. Este é um passo à frente da versão do arquivo nativo do Windows, que foi usada apenas durante o processo de atualização, e não ao referenciar uma biblioteca, levando ao infame "DLL Hell".
Vale ressaltar que a Versão da Versão permite 4 inteiros de 32 bits, enquanto o AssemblyFileVersionAttribute é limitado a 16 bits, pois ele mapeia diretamente para o recurso VERSIONINFO. Assim, se queremos que AssemblyVersionAttribute e AssemblyFileVersionAttribute sejam iguais, isso também coloca um limite nos componentes da versão de montagem.
O Linux, em geral, usa um método diferente para endereçar o controle de versão. Os arquivos binários não possuem um selo de versão incorporado, como a maioria dos binários do Windows. Em vez disso, um nome de arquivo da biblioteca compartilhada indica sua versão, e. /usr/local/lib/mylib. so.1.5.
São criados vários links simbólicos, e. mylib. so - & gt; mylib. so.1 e mylib. so.1 - & gt; mylib. so.1.5. Um aplicativo pode fazer referência a uma biblioteca via link simbólico, como mylib. so.1, e obter a versão compatível 1.x mais recente instalada.
Isso funciona bastante bem, desde que todos sigam esta convenção. Cada biblioteca pode então, por sua vez, carregar bibliotecas, dependendo da mesma maneira.
Os usuários de Linux também estarão familiarizados com o popular "Advanced Package Tool", apt-get, usado de forma ubíqua nos sistemas derivados da Debian, como o Ubuntu. Sendo um verdadeiro gerenciador de pacotes, ele suporta a instalação de versões lado a lado e dependências de rastreamento entre pacotes. Examinamos as vantagens dos gerenciadores de pacotes nas seções a seguir.
Esquemas dos números de versão.
Existem vários esquemas de numeração popular para software, mas todos eles são uma variação do mesmo tema e compartilham características comuns. Ter componentes principais e menores da versão é o mesmo em toda a placa. O que eles representam é bastante consistente:
Maior aumento de número: representa grandes mudanças de falha no sistema de software, muitas vezes não é compatível com versões anteriores, ou adição de grande quantidade de novas funcionalidades. Aumento de número menor: representa mudanças substanciais de evolução, principalmente atualizações ou melhorias na funcionalidade existente ou adição de uma nova nova conjunto de características.
Acima é apenas uma diretriz - não há regras estabelecidas sobre o que as versões maiores e menores devem representar. Só que eles deveriam aumentar à medida que mais recursos são adicionados ao software com o tempo.
Windows e binários especificam um esquema de versão de 4 partes: maior. menor. construir. revisão . Os dois últimos componentes são de forma bastante gratuita, existem muitas variações no que representam - alguns usam contadores de construção incrementais, alguns utilizam a data / hora da compilação, e alguns os derivam dos números de revisão interna do controle de origem.
Muitos ignoram o número da revisão e focam apenas na compilação. O Windows Installer, por exemplo, possui apenas 3 componentes. Se você deseja que sua versão abranja binários e o pacote que contém, então é melhor limitar-se a apenas três números: maior. menor. construir.
Em qualquer caso, o padrão geral: quanto maior for o número da versão, mais recente será o software.
Um popular sistema de controle de versão nos últimos anos (especialmente entre projetos de código aberto) foi apelidado de Versão Semântica (também conhecido como SemVer) e documentado no Semver. Ele apresenta alguns outros componentes, e torna a versão uma string alfanumérica, em vez de um número puro - abrindo algumas possibilidades interessantes.
Os três primeiros componentes são os mesmos que já discutimos, com o patch sendo opcional. O patch é praticamente equivalente ao componente de construção, mas a semântica pode ser diferente. A versão semântica realmente prescreve quando cada componente deve ser incrementado (com base em mudanças de "API pública").
O pré-lançamento, se especificado, é uma seqüência alfanumérica que é usada para marcar uma versão como uma que precede a versão final. Por exemplo, 1.3.567-rc1 precederá 1.3.567. Isso é útil para adicionar mais significado ao rótulo da versão do que simplesmente usando números.
Metadata é outro componente opcional, que permite uma maior marcação do rótulo da versão (geralmente com um timestamp de compilação), mas não participa no pedido de versão, ou seja, as versões que apenas diferem nos metadados são consideradas as mesmas.
Pré-lançamento é útil para gerentes de pacotes como o NuGet, que os tratam de forma diferente - eles são considerados instáveis ​​e não são visíveis para o público em geral, a menos que sejam explicitamente solicitados. Isso permite a liberação de versões alfa / beta sem afetar aqueles que dependem de versões estáveis.
As tags de pré-lançamento também podem ser úteis no fluxo de lançamento interno ao lidar com hotfixes paralelos e compilações privadas, conforme discutido mais adiante neste artigo.
Versioning Non-Binary Files.
Então, sabemos como marcar uma versão nos arquivos binários. Mas e os outros arquivos que compõem um sistema de software - arquivos de configuração, imagens, documentos, fontes, etc.? Como você estampa uma versão neles?
E quanto a frameworks da web como o ASP (ou Ruby, Node. js, Python, etc.), onde arquivos e páginas de origem podem ser modificados no local e atualizados automaticamente? Como podemos corrigir um sistema da Web, ou seja, atualizar alguns arquivos de destino e ainda mantê-los com versão?
A resposta é - não atualize arquivos individuais! Não há como manter um número de versão significativo para o seu aplicativo de software, se os arquivos não binários individuais podem ser atualizados ad-hoc como hotfixes.
Atualize usando um pacote em vez disso.
Importância da compilação e do pacote.
When you hear the term "build", normally the compilation comes to mind - most compiled languages, such as C#, C++ or Java, have to be compiled into a binary before being able to be executed. And so building is commonly associated with the process of compiling .
But that's not an entire picture. Some languages or frameworks, such as Python or ASP, don't strictly require compilation. They can be either interpreted, in Python's case, or compiled on-the-fly, in ASP's case. What should a build do for these systems? How do you "build" a Python app?
That's why it is more helpful to think of build as an assembly process , or simply packaging . Just like a line of consumer goods, e. g. shoes, gets packaged before shipping to the stores, so does a software system, before being released.
A package concept is essential to versioning , because a package is a single collection of the pieces that comprise a software system, or part of it, and can therefore be identified, and stamped with a version . With the right Package Management system (which we look at in the next section), it can be deployed and updated, and specify dependencies on the other packages.
Software today is never a single binary executable file - it is a collection of various binaries, libraries, documents, configuration files, images, and other resources. A package is what helps us group them together, version and release to the outside world.
A package doesn't have to be sophisticated, although it helps in some situations (e. g. databases). It can even be a simple ZIP file, that can contain version in the file name, or embedded as a text file. In fact, many open source projects do just that - a release is a ZIP or a. tar. gz archive.
The important thing is that a package is a single unit, that is released and updated at the same time, leading to consistency . It is common to have several packages, for example, representing "client" and "server" components, or any other logical grouping applicable to a software system. Each package can then be updated on its own.
Let's take a look at some of the common packaging methods, the versioning approach, and which application they are best suited for.
Windows Installer.
Best Suited : Complete Windows GUI Applications, Windows Services, or Drivers.
The oldest, and for a long time the only recommended way, to install applications on a Windows platform. It has a built-in versioning support and a sophisticated (some would say "complicated") set of rules for determining when to update components. While a Windows Installer package (.msi) is a single file, in essence, it is a collection of small logical components (down to single files) that can be updated independently.
Windows Installer will actually check each individual file that is being installed, whether it has a version and whether the version is greater than a file with the same name already installed. That means it is important to version not just the installer package, but each file contained in it. But it also means that it is incredibly difficult to do downgrades (i. e. rollbacks) with Windows Installer.
It is best suited for traditional Windows Applications (GUI, services, drivers) that are released to the public. It is, however, not the best choice for internally developed & distributed applications, any kind of Web applications, or database systems.
It was also used to deploy distributable libraries (native DLLs) and COM objects, but with today's focus on , it is not the right mechanism for distributing libraries.
Web Deploy.
Best Suited : Web Applications (IIS, ASP)
Web Deploy technology was specifically designed for deploying and synchronizing applications on Microsoft IIS web servers. IIS Web Farm replication uses Web Deploy commands and packages behind the scenes to synchronize sites across a set of servers. IIS Manager has an extension (enabled by installing Web Deploy) to "Import Application", which can install or update a web application using a Web Deploy zip package.
Its biggest disadvantage is that it can only be used for web applications on Microsoft IIS platform, and the limited mechanism for customizing installation. While it could be suited for simple web applications, it can quickly become frustrating for anything more sophisticated, i. e. variables, conditional logic, databases, etc.
In addition, it has no inherent support for versioning .
Package Managers.
Best Suited : Shared Libraries, Dependencies, Command-line Utilities.
Package Managers are great for releasing and versioning shared components, and tracking dependencies between them. For example, if you have a shared library that you want others to use, then a Package Manager allows you to publish multiple versions side-by-side, and for consumers of the library to reference the version they depend on. Package Managers can resolve all inter-package dependencies, and retrieve only the versions that are expected. In effect, Package Managers solve the "DLL Hell" problem.
They are best used during development, to resolve library dependencies. However some Package Manager, like Chocolatey for Windows or apt-get for Ubuntu, are geared towards installing complete software.
Most importantly, Package Managers are designed around the versioning concept . So they are a perfect mechanism for distributing versioned software libraries.
For we have NuGet. A lot of open-source libraries have been published to its online repository, and it is now the defacto standard for distributing 3rd party components. It is encouraged that every team sets up their own NuGet repository to share and publish internally developed libraries in a versioned manner.
NuGet can even be used to release complete software systems - see next section.
Other development environments have their own - npm for Node. js, pip for Python, gems for Ruby, apt-get on Linux. Package Managers have been proven to be extremely useful, and have exploded in popularity.
Octopus Deploy.
Best Suited : Internally Developed & Deployed Software.
Octopus uses NuGet as the packaging and versioning shell. It is similar to an installer, only driven by PowerShell, meaning infinite flexibility in how the software is to be deployed. PowerShell already has a great support for configuring Windows Services, IIS Web Applications, Scheduled Tasks, SQL Server, and more.
For internally developed and distributed software (i. e. for a company running home-grown software solutions) this is a perfect release management vehicle. Packages are versioned and pushed to a shared NuGet feed (e. g. a network share), from where Octopus Deploy can release and deploy each package into the appropriate environment.
NuGet here plays a role of the application package/container, with a version stamped on it. Package can be built once, and then deployed as many times as needed to whatever environment.
Versioning & Packaging Databases.
Database versioning is one of the biggest challenges in software projects. Almost every team I encountered, either completely ignored it or had something inadequate in place. It certainly presents a challenge - database systems mix schema definition with actual live data , and there is no single "file" that can be effectively versioned.
We have to recognize the database as an integral part of the software system. One that executes on a proprietary 3rd-party platform (SQL Server, Oracle, PostgreSQL, etc), but the source of which is part of the software definition. It can be compared to script-based systems, such as Node. js or Python, only the scripts are written in a SQL dialect.
There are essentially three popular approaches to database versioning, that support automated deployments (I am not considering manual approaches, because they are error-prone, and have nothing to do with real versioning!).
DB - Migrations.
"Migrations" is a concept where developers keep a set of organized SQL script files, numbered sequentially, where each script applies modifications to the target DB to bring it to the expected state. Whenever a change is needed to the application database, a developer creates a new migration script that applies the delta changes.
All of the scripts are kept as part of the source control, and are packaged with the application (either embedded into the executable binary, or installed along-side). A migrations library then checks the target database for a dedicated table which holds the last "migration script number" applied, and then runs all the scripts with a number greater than that in order, effectively applying all of the changes in turn.
While this approach is simple to implement, and is favored among several popular frameworks (Ruby Rails, Entity Framework), it has a number of significant short-comings . Firstly, there is no single source view of all database objects (i. e. tables, stored procedures, etc), they are sprinkled through the multiple migration scripts. It is not clear which of the scripts contains which of the modifications. One has to "replay" them all to generate a database, and then look directly in the database (rather than source code).
Secondly, the migration scripts number becomes the "version" of the database, which is different from the software package version number for the rest of the application. This is somewhat confusing. In addition, this "version" does not really identify the state of the database, since a database can be changed outside an application without updating the "version". This may potentially break future installs, because migration scripts expect the database to be in a certain state to work.
Thirdly, developers have to be disciplined enough to follow the structure and apply ALL changes through migration scripts . Furthermore, when developing and debugging locally, one often has to go through several iterations before getting that table or store procedure change right. Yet only the final changes should make it into the migration script , meaning they have to be remembered and written manually. Otherwise, migration scripts would contain all of the intermediate changes made by all developers on the project. It is easy to see how that can grow out of proportion quickly.
Finally, there is an argument that migration scripts are a "history of changes", and it is a bit of a redundancy to store them in source control, which already is a "history" of code changes. We would be storing a history of a history . There's something philosophical about that.
Supported by some frameworks and libraries (Rails, DbUp, RoundHousE, EF Code First) Can work with any database Potentially high degree of control over SQL scripts.
Have to manually maintain all migration scripts Tracking changes through source control is difficult Not robust against target database out-of-band changes.
DB - SQL Compare.
Most often this is used in a manual approach, comparing a database between two environments (e. g. development vs test) to copy over the changes. We are considering an automated approach, suitable for the packaging and versioning strategies being discussed.
In source control, database is represent by a series of creation scripts (e. g. to create tables, stored procedures, triggers, etc), such that a new database with the right schema can be created from scratch. Usually each script file logically represents a corresponding object in the database, e. g. Table1.sql would be the create script for Table1 table. All of the scripts are included in the released package (sometimes even combined into a large single create script, by concatenating them).
The idea is that during automated package deployment a temporary fresh database copy is created, by running all of the creation scripts , and then a SQL Compare tool is executed to compare the pristine copy with the target database to generate a migration delta script on the fly.
The advantage of this approach is that it is robust against the target database out-of-band changes, since delta script is generated during deployment , rather than during development. SQL Compare tools (such a RedGate's SQLCompare or XSQL Compare) are sophisticated and mature enough tools that we can have some confidence in the generate SQL code. Each can be controlled by a multitude of options to fine-tune behavior with respect to renames, reordering columns, avoiding drops, etc.
In this case, target database is considered as a runtime environment , and we avoid having the issue of versioning it . Instead we version the package that contains all of the creation scripts , which is much easier, and use it to synchronize target database with what's expected in each version.
The big disadvantage of this approach is the difficulty of getting it right - there is no off-the-shelf framework that would support it, and it has to be developed. For SQL Server, read the next section for a better approach. For others, some day I may put together the set of scripts and logic necessary to achieve this, based on some of my prior work (unless someone else beats me to it).
Automatically detect and migrate changes, regardless of target DB state Only maintaining DDL (i. e. create) scripts in source control, meaning easy change tracking.
More difficult to setup, especially to be automated Having to create a temporary database during each deployment (need " create database " permission)
DB - DACPAC (SQL Server)
For SQL Server there is now a new recommended approach - DACPAC, and it can be produced by Visual Studio 2012 and above, if using the SQL Server database project. Really, this is a slick variation of the "SQL Compare" method above, just that Microsoft has done all the heavy lifting for you!
Essentially, DACPAC is a zip package which contains an XML schema model of what the target database should look like. It is compiled by Visual Studio based on the creation scripts in your project. In fact, it represents that temporary pristine database that we would have had to create manually. Only it is done automatically and the schema represented in an XML format. The real bonus is that a DACPAC can be versioned , i. e. its metadata supports storing a version number.
SQL Server Data Tools can be used to deploy a DACPAC package, which really performs a SQL Compare operation between the in-memory database model loaded from DACPAC and the target database. It does the same thing as SQL Compare, but avoids having to create the extra temporary database copy to do the comparison.
For applications having SQL Server as a back-end, a DACPAC can be included as one of the deployable packages, stamped with appropriate version generated during the build. Starting with SQL Server 2008 R2, database can be registered as a Data-Tier Application, and the latest DAC version is tracked in a system view that can be queried.
Can package the whole DB definition into a single package (or several packages) Can apply the same version to the package as the rest of the software system Same advantages as the SQL Compare method.
SQL Server only Need to treat lookup data in a special way (post-deploy MERGE script)
Build Auto-versioning.
Given the importance of consistent versioning discussed above, it makes sense to implement a strategy for automatically generating and stamping a version number during the software automated build process. We want the version number to be applied to the produced packages, and also applied to all the binaries generated through compilation.
There are several well-known and not so well-known ways of achieving this. We look at pros and cons of each.
Applying Build Number.
There are some who prefer to update the version number manually just before a release. I will argue that this is a bad practice. Firstly, it is easy to forget to do it, if you don't have an automated system for incrementing the version build number. And, if it is easy to forget, it will be forgotten at some point.
Secondly, without automatically updating build number, there will be multiple packages produced from the source code that have the same version number, but different functionality (as more commits are made to the source control). This will be confusing to say the least.
It is better to have a process, like ones described below, where version number build component is automatically updated whenever a non-local build is made.
Multiple Versions for Multiple Components.
If there are multiple software components, where each needs to have its own version number, then it is best to split them each into its own separate build. Don't mix multiple version numbers in the same build, as it unnecessarily increases the complexity, and raises a question about which of the build numbers should be used to label the build itself (in addition to having to tag each source sub-tree separately).
Developer vs Continuous vs Release Builds.
Release build is the one that will potentially be released to public or a particular environment - test, staging, production, etc. That's the build that needs to be consistently versioned to keep track of changes that are included and to link back to the source code at the time of compilation.
Note that the Release build can scheduled - it is popular to have a Daily or Nightly build. In most situations it should be the Release build, i. e. it should be versioned and packaged ready to be released.
Continuous Integration builds run whenever someone commits to the repository and are used to validate that the code compiles, and passes unit tests. There is no need to version this build, as it is not intended to be released.
Developers must also be able to do a Developer build , whether it is to test/fix the build process itself, or to generate shared software components to be used in development. Such builds are intended to be run locally only and should never be publicly released.
You can default the build part of the version number to "0". This will identify Developer builds, i. e. ones that are not supposed to be released. For Release builds pass the build number to your build scripts as a property. Have MSBuild stamp a version number on all generated assemblies and packages.
Tagging Source Control.
Since one of the primary reasons for having a version number is to be able to link back to source code used to build the software (see beginning of the article), it is important to create tags/labels in source control that identify the state of source code at the time that version was built.
Various systems call it differently - TFS has "Labels", Git has "tags". Tag should include the full version (including the build number) of the build, so that it can later be found, if needed.
Build Number - Version File Auto Increment.
Common technique is to record version number together with source code, usually in a separate file (e. g. "version. txt"). The build process then finds the file, reads the version, increments the build number portion, and commits the file back to repository.
If the commit message also includes the version number, e. g "Auto-increment: 1.3.156.0" , then it comes in handy when viewing commit history. You can see the changes that occurred between versions clearly by seeing the commits between the two "Auto-increment: . " messages.
This works fairly well, but has a few drawbacks. Mainly due to the fact that "version" becomes part of the source code. When merging changes between say release branch and main, you have to resort to "cherry-picking" (i. e. selecting just the code changesets) to avoid merging the modified version number. That requires being always careful, because you can accidentally change the versioning sequence of another branch just by merging the "version file" into it.
Control over the build number sequence (i. e. sequential) Can make it easy to see changes between versions in source control history.
Difficult to control merging between code branches in source control.
Build Number - External.
Overcoming the drawbacks of the auto increment approach, it is possible to track the build number outside of the source tree. Build server software such as CruiseControl or TFS Builds can do that - they track a build number internally for each "project" and are able to pass it as a parameter to MSBuild.
Version file is still used, but it records major and minor versions only, and doesn't have to change between each build. This makes it easier to merge changes from release branches back to main and others, since they will contain only code changes, without being intermingled with version increments. Major/minor version changes would occur early in the development cycle, when starting work on the next update, and are already set by the time release branch is created.
Not modifying source tree on every build makes merging between branches easier Versioned builds are forced to be built by a dedicated build server.
Relies on a build system that can supply a build number (e. g. CruiseControl, TFS Builds) Changing build number sequence can be difficult (e. g. TFS Builds)
Build Number - Derived from Date/Time.
A popular alternative is to derive build number for the date/time of the build. The advantage being that it carries more meaning (useful in diagnosis), and each build inherently should get a different build number (with later builds getting a higher number).
The trick, of course, is fitting all this into a 16-bit number, if using the standard 4-part Windows version number. While some solve it by using both, the build and revision components, I cannot recommend it, because revision cannot always be applied to external packages (like Windows Installer, or NuGet), which use only a 3-part version number.
This only allows only 4 unique builds per day, which is not a lot, unless all you want is a daily build .
Not depending on keeping track of the last build number Build number can be given more meaning, if it derives from a date.
Build number is not sequential (but it increases nevertheless) Limited to 16-bit (maximum 65535), so some overflow into revision (4th) number.
Build Number - Derived from Source Control.
A variation of the previous technique is to derive build number from a unique property in source control. With a centralized SCM like Subversion or TFS, a revision or changeset number is an ever increasing number that is tied directly to the source code. The big problem with it is that it can quickly overflow the 16-bit limit, meaning you may have to accept build numbers looping back to zero.
An alternative in distributed SCM, like Git, is to use the size of the commit history log as the build number. This will monotonously increase for any single branch, as new commits are made. It too can overflow the 16-bit limit, but goes a lot further than the global revision number.
Example: git rev-list HEAD --count.
Not depending on keeping track of the last build number No possibility of "forgetting" to update version file, or accidentally merge it to/from another branch.
Commit history size will grow beyond 65,535 at some point, overflowing the 16-bit build number.
Parallel Branches.
It's no secret that developing for multiple versions requires multiple branches in source control, each representing a "version" stream for the software. They can be roughly divided into:
Development branches - where unstable code for the next version lives, and where developers commit daily work Feature branches - veering off from development branches, encorporating larger feature development, that would otherwise disrupt other team members Release branches - representing versions of released software, or a release undergoing stabilization.
Each release branch needs to have an identifying version, and is usually named after it, e. g. "1.7" . A decision of whether to create a new release branch depends on how long it is expected that it will be in stabilization mode before releasing, and whether concurrent live versions are permitted (i. e. for packaged software). If you need to be able to maintain & hotfix the current released version, while a new version is being tested & stabilized, then create a new branch.
Development and feature branches need to have a version number that is above any of the existing release branches to avoid confusion. For example, if a 1.7 release branch is created, for the upcoming 1.7 release, then immediately update development branch version sequence to 1.8 .
Versioning feature branches is more difficult, since you don't want to start a new versioning sequence for every feature . Nothing should be "released" from feature branches, so this version is for internal purposes only. If using Semantic Versioning, attach a prerelease tag to clearly indicate this is a version for a feature branch, e. g. 1.8.781-dev-feature-x .
In any case, you wouldn't deploy anything built from a feature branch to the shared testing or production environment, or release a package from it. So it is acceptable to have version sequence overlap with that of development branch.
Finally, in the next section we look at how to version patches & hotfixes that are applied to release branches.
Handling Patches / Hotfixes.
Devising a system to handle patches depends heavily on the rest of the software development cycle, which is what many teams forget when searching for the "one, true way" of handling concurrent patching of the released/production software in parallel with working on the new version.
For example, having a short QA/test cycle, where most of the tests are automated, results in a more simplified and robust system, which does not have to deal with multiple parallel hotfixes "in test".
Overlapping hotfixes.
One difficulty that comes with managing parallel development is consistent versioning and deployment strategy that would overcome inherent conflicts. Consider following scenario: you have recently released a software package 1.5.167. Two urgent show-stopping issues have slipped past your QA process and now require a quick fix. You assign two developers to work on each one in parallel. How would they commit their fixes to minimize conflicts? How do you test each fix? How do you release one independent of the other?
This is a good example of the complexity of software release processes that can be encountered in real-world teams. It applies both to internal software and packaged software, but distribution of the hotfix might be slightly different for each one.
First, let's consider what happens if we remove concurrency . In the case where the two issues are worked one after the other , the solution becomes simple. The first fix gets committed into the maintenance/hotfix branch for 1.5 release stream, a new build is generated, with an incremented build number. Build goes through a quick QA cycle to make sure there is no regression, and then it is ready to be deployed. Same process repeats for the second fix.
The problem with concurrent approach is the time when development is in parallel, creating the entangled case where there is no build/package that contains only one of the fixes , i. e. independent of the other. This problem is magnified by a slow QA cycle , usually meaning there are no automated tests. While one fix is in test, if a commit for a second fix is made to the same branch, and a problem is discovered with the first one, it becomes very difficult to separate the two now.
The culprit here is, of course, the concept of a partial fix - the state where the fix is not complete. It has been committed, but has a problem with it, requiring further commits . This can easily create the case of a hotfix branch where the two fixes are "entangled" (quantum physics on the code level!).
Solution is to remove possibility of a partial hotfix .
This means that each hotfix has to be coded and tested in a separate code stream, independent of the other. Once tested, and ready for release, it is merged into the main hotfix release branch, where the automated build can create a new package and apply versioning (i. e. increment build number, for example, to 1.5.168).
Second hotfix, once tested, also has to be merged into the main hotfix release branch. But, because during the work on this second hotfix, the first hotfix got released, we first merge the first hotfix into the second hotfix's branch ! This ensures that we can test how the second hotfix operates, when applied on top of the first hotfix, and merge any code conflicts, if any.
In the end, you want a system with both hotfixes applied - that is the "next" version. So it makes sense that whatever hotfix is "second", it is applied on top of the "first" one. And creating a packaged release from the single hotfix release branch ensures that the version number is consistently incremented for the whole system.
Of course, above means that we must create a separate branch for each hotfix. Some version control systems, namely Git, make this very easy and part of the expected developer workflow. If you are using a version control system like TFS, then creating new branches for each hotfix is a bit more painful. In TFS, I suggest using named Shelvesets feature to emulate Git's process, and perform initial QA tests for a hotfix from a Shelveset-branch build. Then commit Shelveset into the hotfix branch to build the official hotfix package (and perform necessary merging).
What about the versioning of the interim hotfix builds ? The main hotfix release branch would have a standard versioning scheme applied (as discussed above), either incrementing a build number, or using a timestamp. Each new hotfix, applied on top of all previous hotfixes, gets an increased build number , and the software version keeps moving forward.
However, when building from the developer hotfix branch (or Shelveset in TFS), we also need to apply a version to distinguish it from other builds, and be able to deploy it into QA/test environment. We want to be able to test each hotfix in isolation, applied on top of an existing released version of the software system. This becomes problematic, if you have a single test environment .
You do not want to apply both hotfixes into one test environment, because there is no guarantee that they won't conflict or affect each other. If you are able to quickly spin up a test environment for a hotfix development branch, then you can truly parallelize team efforts. For a shared test environment, they have to be applied one at a time :
Force install latest release version (e. g. 1.5.168) to bring environment to a known state Install the hotfix version to be tested Perform the tests (preferably automated) For shared test environnments this is the bottleneck, since no other hotfixes can be tested at the same time (automation can help minimize the time spent in this step) Repeat 1-3, until tests are satisfactory.
What this means is that each hotfix has to have its build version number greater than the latest released version, the one it is being applied on top of. There are several ways to achieve that. If using a derived build number , this should just work out of the box. If incrementing or using external build numbers, then the easiest option is to simply force the build for hotfix development branch (or Shelveset) to use a number greater than latest released version (i. e. .168).
With Semantic Versioning, we can setup hotfix builds to use a "prerelease" tag that clearly marks it as a hotfix-test build. For example - 1.5.169-check14761 , where the trailing number could be a reference to the issue tracking system. This works especially well when using NuGet as the packaging mechanism.
Once tested, the changes can be merged into hotfix release branch, and an official build generated, with incremented build version number.
NOTE: Above process to resolve concurrent hotfixes is undoubtedly complicated. It is intended to solve a particular real-world scenario, but one that does not happen too often. If there are no concurrent fixes expected, you can simplify your life by applying fixes directly to the hotfix release branch.
Patching a large system.
If applying hotfixes to a large system, we don't want to upgrade the whole thing, which may involve a lot of different components - services, GUI applications, scheduled jobs, databases, etc. Instead, we want to apply the fix only to affected parts.
This is where splitting the system into multiple packages helps. Each corresponds to a logically contained piece of the system - for example, each service, application, database, etc is its own package. That means they can be patched independently by applying just that package .
Care must be taken about dependencies, if hotfix affects multiple packages at once. Although, in that case, ask yourself is it really a hotfix or a new minor version?
Patching for specific installation.
Some software shops may have developed the practice of patching the software for individual customers (for packaged software), in other words creating a "custom" version for just that installation, without including this fix in the rest of released software streams. This is one of the worst situations to be in, with regards to versioning, since it creates a large number of variations that have to be maintained separately.
Instead, release a general update , moving the overall software version forward for that release stream. Adopt a "feature" system , where parts of the software can be turned on & off based on configuration. If a specific fix is needed for a particular installation, then that code can be encapsulated behind a configuration switch which turns this section of the code on or off. That particular customer can turn it on , while the rest can have it off!
This is also a popular technique in web applications, of which only one installation exists (on the server), where various "features" can be enabled based on "configuration" for each user , or a set of users.
Patching the changes only.
There is often the temptation to simply patch in the changes to the live/production system by editing/replacing one file, or updating one table or stored procedure. The change is small, and it seems like the fastest way to solve the imminent issue, without changing anything else in the system.
While it seems like a smaller risk to make only the necessary updates directly, it makes it a whole lot harder to know the state of the system in the future. As more and more such "small" patches get applied, there is no longer any reliable way to link the running system back to the original source code, making further maintenance exponentially more complicated (and, ironically, increasing the risk).
Updating individual non-binary (e. g. config files) or altering database objects does not update any version number . That means it is difficult to tell which changes have been made to the system, leading to "maintenance hell" (a variation of the infamous "DLL Hell").
Rule of thumb: Any change to the system should change the version number.
NOTE : Windows Installer allows a so called "small update", where product version number does not have to change, used for small hotfix patches. I believe this creates too much confusion, and so I do not recommend it. Windows Installer does track each patch, through package code, so you always know which patches have been applied. But it means now having to track and remove patches on subsequent product updates, which complicates the process. It may work for Microsoft Windows and Microsoft Office, but I wouldn't recommend using it for any system.
Palavras finais.
This turned out to be a much longer article than I originally anticipated when I sat down to write about versioning . I am hoping it proves useful for software engineers out there looking for some guidance on how to apply these concepts in their own projects.
Still this seems like only a partial treatment of the topic.
Everything I wrote above has been learned through the painful process of trial & error over the years. If just a few readers have an "aha!" moment while reading this, then I have achieved my goal!

Versioning Strategies.
Articles in this series.
Once services are pushed to production their associated WSDL documents – which represent service endpoints, protocols and related contracts describing operations and messaging – must not be changed. Or, at a minimum, any changes should be backward compatible so that existing clients are not affected when changes are published. In this section I will discuss how WCF contracts support backward compatibility, and explain a few versioning strategies that you might consider for your WCF applications.
WCF Contracts and Backward Compatibility.
WCF contracts are version tolerant by default. Figure 1 and Figure 2 summarize typical changes to service contracts and data contracts and describe the impact to existing clients. In short, the DataContractSerializer allows missing, non-required data and ignores superfluous data for service contracts, data contracts and similarly, message contracts. Only the removal of operations or the addition or removal of required data causes problems with existing clients.
Figure 1: Service contracts and backward compatibility.
Adding new parameters to an operation signature.
Client unaffected. New parameters initialized to default values at the service.
Removing parameters from an operation signature.
Client unaffected. Superfluous parameters pass by clients are ignored, data lost at the service.
Modifying parameter types.
An exception will occur if the incoming type from the client cannot be converted to the parameter data type.
Modifying return value types.
An exception will occur if the return value from the service cannot be converted to the expected data type in the client version of the operation signature.
Adding new operations.
Client unaffected. Will not invoke operations it knows nothing about.
An exception will occur. Messages sent by the client to the service are considered to be using an unknown action header.
Figure 2: Data contracts and backward compatibility.
Add new non-required members.
Client unaffected. Missing values are initialized to defaults.
Add new required members.
An exception is thrown for missing values.
Remove non-required members.
Data lost at the service. Unable to return the full data set back to the client, for example. Sem exceções.
Remove required members.
An exception is thrown when client receives responses from the service with missing values.
Modify existing member data types.
If types are compatible no exception but may receive unexpected results.
Choosing a Versioning Strategy.
Version tolerance is a good thing since it gives developers a flexible way to handle change. Since the DataContractSerializer is version tolerant, in theory you can refrain from officially versioning contracts and endpoints that expose those contracts until a breaking change is introduced such as removing operations from a service contract, or adding or removing required members from a data contract.
There are also potential side-effects of version tolerance:
If you add new operations to a service contract only clients that reference the latest WSDL will know about those operations and you will not be able to track which of your existing clients have updated their WSDL unless you expose a new endpoint when changes are made. If you add non-required data members to a data contract you may have to write extra code to initialize missing values to something meaningful – as opposed to using the default value. If you remove non-required data members from a data contract you may have round-trip issues where data passed to services or returned to clients is lost. It may be difficult to track problems if you do not keep track of different versions of contracts, as changes are made to production.
It is important to choose an appropriate versioning strategy that satisfies the sometimes conflicting need for agility and productivity vs. change control. Here are a few versioning strategies to consider:
Agile Versioning: Rely on backward compatibility for as long as possible and avoid formal contract and endpoint versioning until compatibility is broken. This approach is useful in agile environments that require frequent updates to production code. Strict Versioning: Perform formal contract and endpoint versioning for any change to a service contract, data contract, message contract or other contract-related or endpoint-related changes. This approach is best in environments what have less frequent production updates or that require detailed tracking of any and all changes. Semi-Strict Versioning: Perform formal contract and endpoint versioning when contracts or endpoints are modified in a way that your policy requires tracking the change. For example, your policy might be to allow new operations to be added to a service contract, but if any changes are made to existing operations, or if any data contracts change such that they are semantically different, it requires versioning. This approach lies somewhere between agile and strict versioning.
The agile approach to versioning – shown in Figure 3 – means making changes to existing data contracts and service contracts without versioning them, or supplying new endpoints. Strict versioning means providing new data contracts, service contracts and endpoints as shown in Figure 4.
Figure 3: Agile versioning through the same service contract and endpoint.
Figure 4: Strict versioning through a new service contract and unique endpoint.
It is best to decide on a versioning policy before you release the first version to production.
Versioning Service Contracts.
To formally version a service contract you should do the following:
Make a copy of the original contract with a new interface type name, the same contract name and a new namespace. Make any required modifications to the contract definition. Implement the new service contract on the same service type if possible. You can use explicit contract implementation to funnel requests to the different method implementations if necessary. Add a new endpoint for the new service contract to the existing <service> configuration. As an alternative, you can use a common base class that implements core service operations and provide overrides in each version’s derived type. This can be a better approach if there are significant changes to the service implementation, or if the service security behaviors will be different from the original service. This will require a separate <service> entry in the configuration file.
Figure 5 shows an example of strict service contract versioning with a shared service implementation. Note that the Name property of the ServiceContractAttribute is the same for both versions of the contract, while the interface type name changes from IServiceA to IServiceA2. Also notice that two service endpoints are configured – one for each contract.
Figure 5: Version 1 and 2 of ServiceAContract with related endpoint configuration.
public interface IServiceA.
public interface IServiceA2.
<endpoint address="ServiceA" binding="wsHttpBinding"
<endpoint address="ServiceA2" binding="wsHttpBinding"
In the accompanying code download for this paper, the following directories contain samples that illustrate service contract versioning: WCFEssentials\ContractVersioning\ServiceContractVersioningAgile, WCFEssentials\ContractVersioning\ServiceContractVersioningStrict.
Versioning Data Contracts.
To formally version data contracts is a little more complicated since each data contract is likely tied to business entities that are used by the service tier, the business tier and the data access tier in the application. I recommend the following approach:
Make a copy of the original data contract and type, but rename the type to V1 since this will no longer be actively used each application tier. Modify the business entity implementation as needed to work with the application, and update its data contract providing a new namespace. Make a copy of the original service contract and implementation and update it to use the V1 type name. Note that this will not alter the service implementation since the V1 type name should have the original data contract with an explicit name and namespace. Version any service contracts that rely on this new data contract following instructions in the previous section. That includes exposing new endpoints. Modify the original service implementation to forward calls to a V2 service instance after mapping the V1 business entity to the V2 business entity.
Figure 6 illustrates this approach.
Figure 6: Versioning data contracts.
Client-side data contracts should also implement IExtensibleDataObject so that V1 clients will preserve unknown data from a V2 service – assuming that non-strict versioning is used. Although you can implement IExtensibleDataObject at the service as well, it is less likely that a service needs to preserve unknown data since it is the system of record for the application. Suppressing IExtensibleDataObject behavior can protect the service from potential DoS attacks alongside message size and reader quotas set on the binding. If you are sharing metadata such as data contract definitions between clients and services – you can implement IExtensibleDataObject on the data contract types and disable support for this extensibility at the service only. This is best done in code so that it can’t be inadvertently changed on production – by setting the IgnoreExtensionDataObject property of the ServiceBehaviorAttribute to true, as shown here:
public class ArticlesManagerService : IArticlesManager.
This can also be configured declaratively in the <behaviors> section as follows:
In the accompanying code download for this paper, the following directories contain samples that illustrate data contract versioning and IExtensibleDataObject: WCFEssentials\ContractVersioning\DataContractVersioning, WCFEssentials\ContractVersioning\IExtensibleDataObject.

Comments

Popular Posts