Wednesday, 11 April 2018

Estratégia de versionamento de software


Estratégias de versionamento de software.
Um perfeccionista preso no mundo real.
A versão de software pode ser uma daquelas áreas em que você nunca se sentiu exatamente correto. Não existe uma orientação definitiva com uma solução que satisfaça a todos. Principalmente as equipes de software estão confusas sobre o assunto ou estão optando por ignorá-lo. Este guia tem como objetivo 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 o Microsoft stack (Windows,), já que é com o que eu tenho mais experiência, mas os princípios se aplicam em geral. Linux, Node. js, Python & amp; Ruby também são levemente tocados.
Versões em todos os lugares.
Estamos todos muito 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. Sequelas de filmes estão sendo versionadas - "Fast & amp; Furious 7" (7 !?), os sapatos estão sendo versionados - "Air Jordan XX8" e, mais popularmente, os livros estão sendo versionados - "One Minute Manager, 1984 edition". Na verdade, olhando para os livros, as pessoas já estão fazendo versões há algum tempo - "Encyclopedia Britannica", desde 1768!
A premissa é simples - à medida que os produtos sobrevivem e continuam sendo aperfeiçoados, os lançamentos mais recentes precisam ser diferenciados dos anteriores. O nome do produto não muda, porque o mercado já se familiarizou com ele, então algo é acrescentado no final para indicar que é mais novo (ou diferente).
Embora as versões existissem muito antes da era digital, o software realmente levou o assunto adiante. 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 de software são muito mais curtos e um potencial para muitas edições simultâneas é muito maior.
Simplesmente usar anos (ou mesmo meses), como nas edições de livros, não é suficiente. Novas versões do software podem ser produzidas em minutos. Além disso, o software possui um aspecto paralelo massivo - fluxos de software - onde várias versões principais podem existir, e todas podem ser continuamente atualizadas ao mesmo tempo. Isso raramente acontece com seus sapatos. (Eu gostaria que sim, às vezes eu não quero atualizar para o modelo de catálogo deste ano, eu 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 por que queremos fazer isso em primeiro lugar! Afinal, se sabemos as razões exatas de por que é útil, podemos julgar melhor se as soluções propostas são adequadas.
Nós fizemos alusão a isso na última seção, referindo-se ao que é chamado de versão pública. Essa é a versão que é publicamente visível e, na maioria das vezes, tem peso de marketing (ou seja, é mais provável que seja definida 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 é nova & amp; brilhante (supondo que as pessoas geralmente querem "new & amp; shiny"). 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, já que ele transmite de maneira simples e eficiente o status atualizado. Os fabricantes de automóveis vêm fazendo isso há muito tempo.
A versão privada é o que estamos acostumados no mundo do software. Um selo interno que (esperançosamente) identifica de forma exclusiva um determinado software. O software, como um carro, pode ser feito de várias partes. Levando a analogia do carro ainda mais, a "versão privada" do carro é o número do chassi do VIN. Os fabricantes lançam e mantêm catálogos volumosos de peças, mapeando para "números de versão" de automóveis. Um mecânico pode então pedir uma peça exata que se ajustaria ao seu veículo.
Sem um "número de peça particular", você não conseguiria consertar seu software na natureza, já que não saberia a "forma" exata que um módulo substituto deve ter para se encaixar no sistema como um todo. Imagine se você fosse forçado a trocar seu carro inteiro quando uma luz traseira quebrasse.
Portanto, o número da versão particular é usado exatamente como um identificador de catálogo. Destina-se a ser usado na solução de problemas ou na manutenção de seu software. (Eu gosto da analogia de "dogtag" de Jeff Attwood!) Deve mapear uma descrição de como é aquela 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 particular. 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 passar o mouse sobre 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 ao qual estamos acostumados: major. minor. build. revision (por exemplo, "1.2.360.0"). É importante observar que cada número é limitado a 16 bits e, portanto, não pode exceder 65535. Isso tem algumas implicações sobre o que podemos representar com esses números.
Note que um rótulo para estes números não é estritamente definido - eles são simples 4 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 controle de versão.
Essa versão é mais usada durante o processo do Windows Update, 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 recente do que o que já está instalado. Se a versão for maior, não há problema em 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 de 4 inteiros. Também podemos definir AssemblyVersionAttribute e AssemblyFileVersionAttribute, que especificam uma versão de assembly e um recurso de versão do Windows, respectivamente.
Em, a versão do assembly existe separadamente da versão básica 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 com mais frequência são as mesmas para evitar confusão.
usa a versão para rastreamento de dependência, ou seja, anotando as versões de assemblies sendo referenciadas, tornando assim óbvio quando uma atualização quebra a compatibilidade para o aplicativo que depende de uma biblioteca específica. Este é um passo em frente da versão nativa do arquivo do Windows, que foi usada apenas durante o processo de atualização, e não ao fazer referência a uma biblioteca, levando ao infame "Inferno das DLLs".
Vale a pena notar que a versão de s permite 4 inteiros de 32 bits, enquanto AssemblyFileVersionAttribute é limitado a 16 bits, como mapeia diretamente para o recurso VERSIONINFO. Portanto, se quisermos que AssemblyVersionAttribute e AssemblyFileVersionAttribute sejam os mesmos, 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 contêm um carimbo 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, por exemplo /usr/local/lib/mylib. so.1.5.
Um número de links simbólicos é criado, por ex. mylib. so - & gt; mylib. so.1 e mylib. so.1 - & gt; mylib. so.1.5. Um aplicativo pode fazer referência a uma biblioteca por meio de link simbólico, como mylib. so.1, e obter a versão compatível com 1.x mais recente instalada.
Isso funciona razoavelmente bem, desde que todos sigam essa convenção. Cada biblioteca pode, por sua vez, carregar bibliotecas das quais depende de maneira semelhante.
Os usuários de Linux também estariam familiarizados com o popular "Advanced Package Tool", apt-get, usado de forma onipresente nos sistemas derivados do Debian, como o Ubuntu. Sendo um verdadeiro Gerenciador de Pacotes, ele suporta a instalação de versões lado-a-lado e rastreamento de dependências entre pacotes. Analisamos mais de perto as vantagens dos gerentes de pacotes nas seções a seguir.
Esquemas de números de versão.
Existem vários esquemas de numeração de versões populares para software, mas todos são uma variação do mesmo tema e compartilham traços comuns. Ter componentes principais e secundários da versão é o mesmo em toda a linha. O que eles representam é bastante consistente:
Major number increase: representa grandes mudanças no sistema de software, muitas vezes não compatíveis, ou adição de grande quantidade de novas funcionalidades. Menor aumento de número: representa mudanças evolutivas menos substanciais, principalmente atualizações ou melhorias na funcionalidade existente, ou adição de um novo menor conjunto de características.
Acima é apenas uma diretriz - não há regras definidas sobre o que versões maiores e menores devem representar. Só que eles devem aumentar à medida que mais recursos são adicionados ao software com o tempo.
O Windows e os binários especificam um esquema de versão de 4 partes: major. menor. construir. revisão . Os dois últimos componentes são bastante livres, há muitas variações no que eles representam - alguns usam contadores de compilação incrementais, alguns usam data / hora da compilação e alguns os derivam dos números de revisão internos do controle de origem.
Muitos ignoram o número de revisão e concentram-se apenas na compilação. O Windows Installer, por exemplo, possui apenas 3 componentes. Se você quer que sua versão cubra tanto os binários quanto o pacote contendo, então é melhor limitar-se a apenas três números: maior. menor. construir.
Em qualquer caso, o padrão geral: quanto maior o número da versão, mais recente é o software.
Um esquema de versionamento popular nos últimos anos (especialmente entre projetos de código aberto) foi apelidado de Semântica, e documentado em semver. Ele introduz alguns outros componentes e torna a versão uma cadeia 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, na verdade, prescreve quando cada componente deve ser incrementado (com base nas alterações da "API pública").
O pré-lançamento, se especificado, é uma cadeia alfanumérica usada para marcar uma versão como uma que precede a versão final. Por exemplo, 1.3.567-rc1 irá preceder 1.3.567. Isso é útil para atribuir mais significado ao rótulo da versão do que simplesmente usar números.
Metadados é outro componente opcional, que permite marcação adicional do rótulo da versão (geralmente com um registro de data e hora de compilação), mas não participa da ordenação de versões, ou seja, versões que diferem apenas em 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 liberar versões alfa / beta sem afetar as pessoas que confiam em versões estáveis.
As marcas de pré-lançamento também podem ser úteis no fluxo de release interno ao lidar com hotfixes paralelos e compilações particulares, conforme discutido posteriormente neste artigo.
Arquivos não binários de versão.
Então, sabemos como carimbar uma versão nos arquivos binários. Mas e os outros arquivos que compreendem um sistema de software - arquivos de configuração, imagens, documentos, fontes, etc? Como você estampa uma versão neles?
E quanto aos frameworks web, como ASP (ou Ruby, Node. js, Python, etc), onde os arquivos e páginas fonte podem ser modificados in-loco e automaticamente atualizados? 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 seu aplicativo de software, se arquivos não binários individuais puderem ser atualizados ad-hoc como hotfixes.
Atualize usando um pacote.
Importância do Build e Package.
Quando você ouve o termo "build", normalmente a compilação vem à mente - a maioria das linguagens compiladas, como C #, C ++ ou Java, tem que ser compilada em um binário antes de poder ser executada. E assim, o edifício é comumente associado ao processo de compilação.
Mas isso não é uma imagem inteira. Algumas linguagens ou estruturas, como Python ou ASP, não requerem compilação. Eles podem ser interpretados, no caso do Python, ou compilados on-the-fly, no caso do ASP. O que uma build deve fazer para esses sistemas? Como você "constrói" um aplicativo Python?
É por isso que é mais útil pensar em construir como um processo de montagem ou simplesmente empacotamento. Assim como uma linha de bens de consumo, por ex. sapatos, é embalado antes do envio para as lojas, o mesmo acontece com um sistema de software, antes de ser lançado.
Um conceito de pacote é essencial para a versão, porque um pacote é uma coleção única das partes que compõem um sistema de software, ou parte dele, e, portanto, pode ser identificada e carimbada com uma versão. Com o sistema de gerenciamento de pacotes correto (que veremos na próxima seção), ele pode ser implantado e atualizado e especificar dependências nos outros pacotes.
O software hoje nunca é um único arquivo executável binário - é uma coleção de vários binários, bibliotecas, documentos, arquivos de configuração, imagens e outros recursos. Um pacote é o que nos ajuda a agrupá-los, lançá-los e lançá-los para o mundo exterior.
Um pacote não precisa ser sofisticado, embora ajude em algumas situações (por exemplo, bancos de dados). Pode até ser um arquivo ZIP simples, que pode conter uma versão no nome do arquivo ou incorporado como um arquivo de texto. Na verdade, muitos projetos de código aberto fazem exatamente isso - uma versão é um arquivo ZIP ou. tar. gz.
O importante é que um pacote seja uma única unidade, que é liberada e atualizada ao mesmo tempo, levando à consistência. É comum ter vários pacotes, por exemplo, representando componentes "cliente" e "servidor", ou qualquer outro agrupamento lógico aplicável a um sistema de software. Cada pacote pode ser atualizado por conta própria.
Vamos dar uma olhada em alguns dos métodos comuns de empacotamento, na abordagem de controle de versão e em qual aplicativo eles são mais adequados.
Instalador do Windows.
Melhor Adequado: Aplicativos Completos de GUI do Windows, Serviços do Windows ou Drivers.
O mais antigo, e por muito tempo o único caminho recomendado, para instalar aplicativos em uma plataforma Windows. Ele tem um suporte de versão embutido e um conjunto de regras sofisticado (alguns diriam "complicado") para determinar quando atualizar componentes. Enquanto um pacote do Windows Installer (.msi) é um único arquivo, em essência, é uma coleção de pequenos componentes lógicos (até arquivos únicos) que podem ser atualizados independentemente.
O Windows Installer verificará, na verdade, cada arquivo individual que está sendo instalado, se tem uma versão e se a versão é maior que um arquivo com o mesmo nome já instalado. Isso significa que é importante para a versão não apenas o pacote do instalador, mas cada arquivo contido nele. Mas isso também significa que é incrivelmente difícil fazer downgrades (ou seja, rollbacks) com o Windows Installer.
É mais adequado para aplicativos tradicionais do Windows (GUI, serviços, drivers) que são liberados para o público. Não é, no entanto, a melhor escolha para & amp; aplicativos distribuídos, qualquer tipo de aplicativo da Web ou sistemas de banco de dados.
Ele também foi usado para implantar bibliotecas distribuíveis (DLLs nativas) e objetos COM, mas com o foco de hoje em diante, não é o mecanismo correto para distribuir bibliotecas.
Implantação da Web.
Melhor Adequado: Aplicações Web (IIS, ASP)
A tecnologia Web Deploy foi especificamente projetada para implantar e sincronizar aplicativos em servidores da Web Microsoft IIS. A replicação do IIS Web Farm usa comandos e pacotes do Web Deploy nos bastidores para sincronizar sites em um conjunto de servidores. O Gerenciador do IIS tem uma extensão (habilitada pela instalação do Web Deploy) para "Importar aplicativo", que pode instalar ou atualizar um aplicativo da Web usando um pacote zip do Web Deploy.
Sua maior desvantagem é que ele só pode ser usado para aplicativos da Web na plataforma Microsoft IIS e o mecanismo limitado para personalizar a instalação. Embora possa ser adequado para aplicações Web simples, pode rapidamente tornar-se frustrante para qualquer coisa mais sofisticada, isto é, variáveis, lógica condicional, bases de dados, etc.
Além disso, não possui suporte inerente para controle de versão.
Gestores de Pacotes.
Melhor Adequado: Bibliotecas Compartilhadas, Dependências, Utilitários de Linha de Comando.
Os gerentes de pacotes são ótimos para liberar e versionar componentes compartilhados e acompanhar as dependências entre eles. Por exemplo, se você tiver uma biblioteca compartilhada que deseja que outras pessoas usem, um Gerenciador de Pacotes permite que você publique várias versões lado a lado e que os consumidores da biblioteca façam referência à versão da qual dependem. Os Gerenciadores de Pacotes podem resolver todas as dependências entre pacotes e recuperar apenas as versões esperadas. Na verdade, os gerenciadores de pacotes resolvem o problema "DLL Hell".
Eles são melhor usados ​​durante o desenvolvimento para resolver dependências de bibliotecas. No entanto, alguns gerenciadores de pacotes, como Chocolatey para Windows ou apt-get para Ubuntu, são voltados para a instalação de software completo.
Mais importante ainda, os gerentes de pacotes são projetados em torno do conceito de versão. Então, eles são um mecanismo perfeito para distribuir bibliotecas de software com versão.
Pois nós temos o NuGet. Muitas bibliotecas de código aberto foram publicadas em seu repositório on-line e agora é o padrão de fato para distribuir componentes de terceiros. É encorajado que cada equipe configure seu próprio repositório NuGet para compartilhar e publicar bibliotecas desenvolvidas internamente de maneira com versão.
O NuGet pode até mesmo ser usado para liberar sistemas completos de software - veja a próxima seção.
Outros ambientes de desenvolvimento têm seus próprios - npm para Node. js, pip para Python, gems para Ruby, apt-get no Linux. Os gerentes de pacotes provaram ser extremamente úteis e explodiram em popularidade.
Octopus Deploy.
Melhor Adequado: Desenvolvido Internamente & amp; Software implantado.
O Octopus usa o NuGet como o shell de empacotamento e controle de versão. É semelhante a um instalador, apenas orientado pelo PowerShell, o que significa flexibilidade infinita na maneira como o software deve ser implantado. O PowerShell já tem um ótimo suporte para configurar os Serviços do Windows, os aplicativos da Web do IIS, as tarefas agendadas, o SQL Server e muito mais.
Para software desenvolvido internamente e distribuído (ou seja, para uma empresa que executa soluções de software desenvolvidas em casa), este é um veículo de gerenciamento de liberação perfeito. Pacotes são versionados e enviados para um feed NuGet compartilhado (por exemplo, um compartilhamento de rede), de onde o Octopus Deploy pode liberar e implantar cada pacote no ambiente apropriado.
O NuGet aqui desempenha uma função do pacote / contêiner do aplicativo, com uma versão estampada nele. O pacote pode ser criado uma vez e depois implantado quantas vezes forem necessárias para qualquer ambiente.
Versioning & amp; Bancos de dados de empacotamento.
Versão de banco de dados é um dos maiores desafios em projetos de software. Quase todo time que eu encontrei, ou ignorou completamente ou teve algo inadequado no lugar. Ele certamente apresenta um desafio - os sistemas de banco de dados combinam a definição do esquema com os dados reais, e não há um único "arquivo" que possa ser efetivamente versionado.
Temos que reconhecer o banco de dados como parte integrante do sistema de software. Um que é executado em uma plataforma proprietária de terceiros (SQL Server, Oracle, PostgreSQL, etc), mas cuja origem faz parte da definição do software. Pode ser comparado a sistemas baseados em script, como o Node. js ou o Python, somente os scripts são escritos em um dialeto SQL.
Existem essencialmente três abordagens populares para o versionamento de banco de dados, que suportam implantações automatizadas (não estou considerando abordagens manuais, porque elas são propensas a erros e não têm nada a ver com versões reais!).
BD - Migrações.
"Migrações" é um conceito em que os desenvolvedores mantêm um conjunto de arquivos de script SQL organizados, numerados sequencialmente, em que cada script aplica modificações no banco de dados de destino para trazê-lo ao estado esperado. Sempre que uma mudança é necessária no banco de dados do aplicativo, um desenvolvedor cria um novo script de migração que aplica as alterações delta.
Todos os scripts são mantidos como parte do controle de origem e são empacotados com o aplicativo (seja incorporado no binário executável ou instalado ao lado). Em seguida, uma biblioteca de migrações verifica no banco de dados de destino uma tabela dedicada que contém o último "número de script de migração" aplicado e, em seguida, executa todos os scripts com um número maior do que em ordem, aplicando efetivamente todas as alterações.
Embora essa abordagem seja simples de implementar e seja usada entre várias estruturas populares (Ruby Rails, Entity Framework), ela apresenta várias deficiências significativas. Em primeiro lugar, não há uma única visualização de origem de todos os objetos de banco de dados (por exemplo, tabelas, procedimentos armazenados, etc.), eles são espalhados por meio dos vários scripts de migração. Não está claro qual dos scripts contém qual das modificações. É preciso "repetir" todos para gerar um banco de dados e, em seguida, procurar diretamente no banco de dados (em vez do código-fonte).
Em segundo lugar, o número de scripts de migração torna-se a "versão" do banco de dados, que é diferente do número da versão do pacote de software para o restante do aplicativo. Isso é um pouco confuso. Além disso, essa "versão" não identifica realmente o estado do banco de dados, já que um banco de dados pode ser alterado fora de um aplicativo sem atualizar a "versão". Isso pode interromper futuras instalações, porque os scripts de migração esperam que o banco de dados esteja em um determinado estado para funcionar.
Em terceiro lugar, os desenvolvedores precisam ser disciplinados o suficiente para seguir a estrutura e aplicar TODAS as mudanças através de scripts de migração. Além disso, ao desenvolver e depurar localmente, geralmente é necessário passar por várias iterações antes de fazer com que a tabela ou o procedimento de armazenamento sejam alterados corretamente. No entanto, apenas as alterações finais devem entrar no script de migração, o que significa que elas devem ser lembradas e escritas manualmente. Caso contrário, os scripts de migração conteriam todas as alterações intermediárias feitas por todos os desenvolvedores no projeto. É fácil ver como isso pode crescer rapidamente.
Por fim, há um argumento de que os scripts de migração são um "histórico de alterações" e é um pouco redundante armazená-los no controle de origem, que já é um "histórico" de alterações de código. Estaríamos armazenando uma história de uma história. Há algo de filosófico nisso.
Suportado por algumas estruturas e bibliotecas (Rails, DbUp, RoundHousE, Código EF First) Pode trabalhar com qualquer banco de dados Potencialmente alto grau de controle sobre scripts SQL.
Tem que manter manualmente todos os scripts de migração É difícil acompanhar as alterações através do controle de origem Não é robusto contra as alterações fora de banda do banco de dados de destino.
DB - SQL Compare.
Na maioria das vezes, isso é usado em uma abordagem manual, comparando um banco de dados entre dois ambientes (por exemplo, desenvolvimento vs teste) para copiar as alterações. Estamos considerando uma abordagem automatizada, adequada para as estratégias de empacotamento e versionamento que estão sendo discutidas.
No controle de origem, o banco de dados é representado por uma série de scripts de criação (por exemplo, para criar tabelas, procedimentos armazenados, gatilhos, etc.), de modo que um novo banco de dados com o esquema correto possa ser criado do zero. Geralmente, cada arquivo de script representa logicamente um objeto correspondente no banco de dados, por ex. Table1.sql seria o script de criação para a tabela Tabela1. Todos os scripts estão incluídos no pacote lançado (às vezes até combinados em um único script de criação grande, concatenando-os).
A ideia é que, durante a implantação automatizada de pacotes, uma cópia temporária do banco de dados nova seja criada, executando todos os scripts de criação e uma ferramenta SQL Compare seja executada para comparar a cópia original com o banco de dados de destino para gerar um script delta de migração em tempo real. .
A vantagem dessa abordagem é que ela é robusta em relação às alterações fora de banda do banco de dados de destino, já que o script delta é gerado durante a implementação, e não durante o desenvolvimento. As ferramentas do SQL Compare (como o SQLCompare ou o XSQL Compare do RedGate) são ferramentas sofisticadas e maduras o suficiente para que possamos ter alguma confiança no código SQL gerado. Cada um pode ser controlado por uma infinidade de opções para ajustar o comportamento com relação a renomeações, reordenando colunas, evitando quedas, etc.
Nesse caso, o banco de dados de destino é considerado como um ambiente de tempo de execução e evitamos ter o problema de fazer o versionamento dele. Em vez disso, versamos o pacote que contém todos os scripts de criação, o que é muito mais fácil, e o usamos para sincronizar o banco de dados de destino com o que é esperado em cada versão.
A grande desvantagem dessa abordagem é a dificuldade de acertar - não há uma estrutura pronta que a suporte, e precisa ser desenvolvida. Para o SQL Server, leia a próxima seção para uma melhor abordagem. Para outros, algum dia eu posso reunir o conjunto de scripts e lógica necessários para conseguir isso, com base em alguns dos meus trabalhos anteriores (a menos que alguém me impeça).
Detectar e migrar automaticamente as alterações, independentemente do estado do banco de dados de destino. Somente manter scripts de DDL (ou seja, criar) no controle de origem, o que significa fácil controle de alterações.
Mais difícil de configurar, especialmente para ser automatizado Ter que criar um banco de dados temporário durante cada implantação (precisa da permissão "criar banco de dados")
DB - DACPAC (SQL Server)
Para o SQL Server, agora há uma nova abordagem recomendada - DACPAC, e pode ser produzida pelo Visual Studio 2012 e superior, se estiver usando o projeto de banco de dados do SQL Server. Realmente, esta é uma variação do método "SQL Compare" acima, apenas que a Microsoft fez todo o trabalho pesado para você!
Essencialmente, o DACPAC é um pacote zip que contém um modelo de esquema XML de como deve ser o banco de dados de destino. É compilado pelo Visual Studio com base nos scripts de criação do seu projeto. Na verdade, ele representa aquele banco de dados intacto e temporário que teríamos que criar manualmente. Só é feito automaticamente e o esquema é representado em um formato XML. O verdadeiro bônus é que um DACPAC pode ser versionado, ou seja, seus metadados suportam o armazenamento de um número de versão.
O SQL Server Data Tools pode ser usado para implantar um pacote DACPAC, que realmente executa uma operação SQL Compare entre o modelo de banco de dados na memória carregado do DACPAC e o banco de dados de destino. Ele faz a mesma coisa que o SQL Compare, mas evita ter que criar a cópia do banco de dados temporário extra para fazer a comparação.
Para aplicativos com o SQL Server como back-end, um DACPAC pode ser incluído como um dos pacotes implantáveis, estampado com a versão apropriada gerada durante a compilação. A partir do SQL Server 2008 R2, o banco de dados pode ser registrado como um aplicativo da camada de dados, e a última versão do DAC é rastreada em uma visualização do sistema que pode ser consultada.
Pode empacotar toda a definição de DB em um único pacote (ou vários pacotes) Pode aplicar a mesma versão ao pacote como o resto do sistema de software. As mesmas vantagens que o método SQL Compare.
Somente SQL Server Precisa tratar os dados de pesquisa de uma maneira especial (pós-implementação do script MERGE)
Crie versões automáticas.
Dada a importância de versões consistentes discutidas acima, faz sentido implementar uma estratégia para gerar e carimbar automaticamente um número de versão durante o processo de criação automatizado do software. Queremos que o número da versão seja aplicado aos pacotes produzidos e também aplicado a todos os binários gerados através da compilação.
Existem várias formas bem conhecidas e não tão conhecidas de se conseguir isso. Nós olhamos pros e contras de cada um.
Aplicando o Número da Construção.
Há alguns que preferem atualizar o número da versão manualmente antes de um lançamento. Eu argumentarei que esta é uma prática ruim. Em primeiro lugar, é fácil esquecer de fazê-lo, se você não tiver um sistema automatizado para incrementar o número de compilação da versão. E, se for fácil esquecer, será esquecido em algum momento.
Em segundo lugar, sem atualizar automaticamente o número de compilação, haverá vários pacotes produzidos a partir do código-fonte que possuem o mesmo número de versão, mas com funcionalidade diferente (à medida que mais commits são feitos no controle de origem). Isso será confuso para dizer o mínimo.
É melhor ter um processo, como os descritos abaixo, em que o componente de compilação do número de versão é atualizado automaticamente sempre que uma compilação não local é feita.
Múltiplas versões para vários componentes.
Se houver vários componentes de software, em que cada um precisa ter seu próprio número de versão, é melhor dividi-los em sua própria construção separada. Não misture vários números de versão na mesma compilação, pois isso aumenta desnecessariamente a complexidade e levanta uma questão sobre qual dos números de compilação deve ser usado para rotular a própria compilação (além de ter que marcar cada subárvore de origem separadamente ).
Developer vs Continuous vs Release Builds.
A compilação de lançamento é a que potencialmente será lançada em público ou em um ambiente específico - teste, teste, produção etc. Essa é a compilação que precisa ser versionada de forma consistente para acompanhar as alterações incluídas e vincular novamente ao código-fonte no momento da compilação.
Observe que a compilação Release pode ser programada - é popular ter uma compilação diária ou noturna. Na maioria das situações, deve ser a versão de lançamento, ou seja, deve ser versionado e empacotado pronto para ser lançado.
A Integração Contínua é executada sempre que alguém se compromete com o repositório e é usada para validar que o código compila e passa nos testes de unidade. Não há necessidade de versão desta compilação, pois ela não deve ser liberada.
Os desenvolvedores também devem ser capazes de desenvolver um desenvolvedor, seja para testar / corrigir o próprio processo de criação ou para gerar componentes de software compartilhados para serem usados ​​no desenvolvimento. Essas compilações devem ser executadas localmente e nunca devem ser liberadas publicamente.
Você pode padronizar a parte de construção do número da versão para "0". Isso identificará as compilações do desenvolvedor, ou seja, aquelas que não devem ser lançadas. For Release builds passa o número da compilação para seus scripts de compilação como uma propriedade. Faça com que o MSBuild imprima um número de versão em todos os assemblies e pacotes gerados.
Marcando o Controle de Origem.
Como uma das principais razões para ter um número de versão é poder vincular-se ao código-fonte usado para criar o software (consulte o início do artigo), é importante criar tags / rótulos no controle de origem que identifiquem o estado de código-fonte no momento em que a versão foi criada.
Vários sistemas chamam isso de forma diferente - o TFS tem "Labels", o Git tem "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!

Software versioning strategy


I basically follow this pattern:
when it's ready I branch the code in the source repo, tag 0.1.0 and create the 0.1.0 branch, the head/trunk becomes 0.2.0-snapshot or something similar.
I add new features only to the trunk, but backport fixes to the branch and in time I release from it 0.1.1, 0.1.2, .
I declare version 1.0.0 when the product is considered feature complete and doesn't have major shortcomings.
from then on - everyone can decide when to increment the major version.
I use this rule for my applications:
. y = feature number, 0-9. Increase this number if the change contains new features with or without bug fixes. z = hotfix number, 0-
. Increase this number if the change only contains bug fixes.
For new application, the version number starts with 1.0.0. If the new version contains only bug fixes, increase the hotfix number so the version number will be 1.0.1. If the new version contains new features with or without bug fixes, increase the feature number and reset the hotfix number to zero so the version number will be 1.1.0. If the feature number reaches 9, increase the main version number and reset the feature and hotfix number to zero (2.0.0 etc)
We use a. b.c. d where.
a - major (incremented on delivery to client) b - minor (incremented on delivery to client) c - revision (incremented on internal releases) d - build (incremented by cruise control)
Yet another example for the A. B.C approach is the Eclipse Bundle Versioning. Eclipse bundles rather have a fourth segment:
In Eclipse, version numbers are composed of four (4) segments: 3 integers and a string respectively named major. minor. service. qualifier . Each segment captures a different intent:
the major segment indicates breakage in the API the minor segment indicates "externally visible" changes the service segment indicates bug fixes and the change of development stream the qualifier segment indicates a particular build.
There is also the date versioning scheme, eg: YYYY. MM , YY. MM , YYYYMMDD.
It is quite informative because a first look gives an impression about the release date. But i prefer the x. y.z scheme, because i always want to know a product's exact point in its life cycle (Major. minor. release)
The basic answer is "It depends".
What is your objective in versioning? Many people use version. revision. build and only advertise version. revision to the world as that's a release version rather than a dev version. If you use the check-in 'version' then you'll quickly find that your version numbers become large.
If you are planning your project then I'd increment revision for releases with minor changes and increment version for releases with major changes, bug fixes or functionality/features. If you are offering beta or nightly build type releases then extend the versioning to include the build and increment that with every release.
Still, at the end of the day, it's up to you and it has to make sense to you.
As Mahesh says: I would use x. y.z kind of versioning.
x - major release y - minor release z - build number.
you may want to add a datetime, maybe instead of z.
You increment the minor release when you have another release. The major release will probably stay 0 or 1, you change that when you really make major changes (often when your software is at a point where its not backwards compatible with previous releases, or you changed your entire framework)
You know you can always check to see what others are doing. Open source software tend to allow access to their repositories. For example you could point your SVN browser to svn. doctrine-project and take a look at the versioning system used by a real project.
Version numbers, tags, it's all there.
We follow a. b.c approach like:
increament 'a' if there is some major changes happened in application. Like we upgrade 1.1 application to 3.5.
increament 'b' if there is some minor changes like any new CR or Enhancement is implemented.
increament 'c' if there is some defects fixes in the code.
I start versioning at the lowest (non hotfix) segement. I do not limit this segment to 10. Unless you are tracking builds then you just need to decide when you want to apply an increment. If you have a QA phase then that might be where you apply an increment to the lowest segment and then the next segement up when it passes QA and is released. Leave the topmost segment for Major behavior/UI changes.
If you are like me you will make it a hybrid of the methods so as to match the pace of your software's progression.
I think the most accepted pattern a. b.c. or a. b.c. d especially if you have QA/Compliance in the mix. I have had so much flack around date being a regular part of versions that I gave it up for mainstream.
I do not track builds so I like to use the a. b.c pattern unless a hotfix is involved. When I have to apply a hotfix then I apply parameter d as a date with time. I adopted the time parameter as d because there is always the potential of several in a day when things really blow up in production. I only apply the d segment (YYYYMMDDHHNN) when I'm diverging for a production fix.
I personally wouldn't be opposed to a software scheme of va. b revc where c is YYYYMMDDHHMM or YYYYMMDD.
All that said. If you can just snag a tool to configure and run with it will keep you from the headache having to marshall the opinion facet of versioning and you can just say "use the tool". because everyone in the development process is typically so compliant.

Feature branching your way to greatness.
Or task branching your way there. Or release branching. You choose.
Almost all version control systems today support branches–independent lines of work that stem from one central code base. Depending on your version control system, the main branch may be called master, mainline, default, or trunk. Developers can create their own branches from the main code line and work independently alongside it.
Why bother with branching?
Branching allows teams of developers to easily collaborate inside of one central code base. When a developer creates a branch, the version control system creates a copy of the code base at that point in time. Changes to the branch don't affect other developers on the team. This is a good thing, obviously, because features under development can create instability, which would be highly disruptive if all work was happening on the main code line. But branches need not live in solitary confinement. Developers can easily pull down changes from other developers to collaborate on features and ensure their private branch doesn’t diverge too far from the master.
Branches aren't just good for feature work. Branches can insulate the team from important architectural changes like updating frameworks, common libraries, etc.
Three branching strategies for agile teams.
Branching models often differ between teams, and are the subject of much debate in the software community. One big theme is how much work should remain in a branch before getting merged back into master.
Release branching.
Release branching refers to the idea that a release is contained entirely within a branch. This means that late in the development cycle, the release manager will create a branch from the master (e. g., “1.1 development branch”). All changes for the 1.1 release need to be applied twice: once to the 1.1 branch and then to the master code line. Working with two branches is extra work for the team and it's easy to forget to merge to both branches. Release branches can be unwieldy and hard to manage as many people are working on the same branch. We’ve all felt the pain of having to merge many different changes on one single branch. If you must do a release branch, create the branch as close to the actual release as possible.
Release branching is an important part of supporting versioned software out in the market. A single product may have several release branches (e. g., 1.1, 1.2, 2.0) to support sustaining development. Keep in mind that changes in earlier versions (i. e., 1.1) may need to be merged to later release branches (i. e., 1.2, 2.0). Check out our webinar below to learn more about managing release branches with Git.
Feature branching.
Feature branches are often coupled with feature flags–"toggles" that enable or disable a feature within the product. That makes it easy to deploy code into master and control when the feature is activated, making it easy to initially deploy the code well before the feature is exposed to end-users.
Another benefit of feature flags is that the code can remain within the build but inactive while it's in development. If something goes awry when the feature is enabled, a system admin can revert the feature flag and get back to a known good state rather than have to deploy a new build.
Task Branching.
At Atlassian, we focus on a branch-per-task workflow. Every organization has a natural way to break down work in individual tasks inside of an issue tracker, like Jira Software. Issues then becomes the team's central point of contact for that piece of work. Task branching, also known as issue branching, directly connects those issues with the source code. Each issue is implemented on its own branch with the issue key included in the branch name. It’s easy to see which code implements which issue: just look for the issue key in the branch name. With that level of transparency, it's easier to apply specific changes to master or any longer running legacy release branch.
Since agile centers around user stories, task branches pair well with agile development. Each user story (or bug fix) lives within its own branch, making it easy to see which issues are in progress and which are ready for release. For a deep-deep dive into task branching (sometimes called issue branching or branch-per-issue), grab some popcorn and check out the webinar recording below–one of our most popular ever.
Now meet branching's evil twin: the merge.
We’ve all endured the pain of trying to integrate multiple branches into one sensible solution. Traditionally, centralized version control systems like Subversion have made merging a very painful operation. But newer version control systems like Git and Mercurial take a different approach to tracking versions of files that live on different branches.
Branches tend to be short-lived, making them easier to merge and more flexible across the code base. Between the ability to frequently and automatically merge branches as part of continuous integration (CI), and the fact that short-lived branches simply contain fewer changes, "merge hell" becomes is a thing of the past for teams using Git and Mercurial.
That's what makes task branching so awesome!
Validate, validate, validate.
A version control system can only go so far in affecting the outcome of a merge. Automated testing and continuous integration are critical as well. Most CI servers can automatically put new branches under test, drastically reducing the number of "surprises" upon the final merge upstream and helping to keep the main code line stable.

Software versioning strategy


This question already has an answer here:
I would be interested to get the SO community's opinions on the best application versioning strategy.
How do you keep track of your application's version number? Do you have a formal definition of what each number/character in that version represents?
What do the different numbers/strings in the application's version mean for your app?
Do you use any automated updating system in your apps (e. g. something like Sparkle) and how good has it been behaving for you?
Do you have a separate update procedure for beta-testers or pre-release testers of your app?
marked as duplicate by gnat, MichaelT, Kilian Foth, GlenH7, Rein Henrichs Apr 29 '13 at 2:42.
Esta pergunta foi feita antes e já tem uma resposta. If those answers do not fully address your question, please ask a new question.
migrated from stackoverflow May 18 '11 at 13:48.
This question came from our site for professional and enthusiast programmers.
How do you keep track of your application's version number? Do you have a formal definition of what each number/character in that version represents?
What do the different numbers/strings in the application's version mean for your app?
I use following:
Major - Major version is a definite release of the product. It increased when there are significant changes in functionality.
Minor - Minor version is incremented when only new features or major bug fixes have been added.
Upgrade/Patch - Upgrade refers to the replacement of a product with a newer version of product. It is incremented only when upgrade is provided on designated major release. Patch version starts with 0 and incremented only when bug has been resolved.
Build No - Build Number is incremented when new build is created.
Do you use any automated updating system in your apps (e. g. something like Sparkle) and how good has it been behaving for you?
We use building tool which automatically builds app at night which we call nightly build and this increases build number every time a build is created.
Do you have a separate update procedure for beta-testers or pre-release testers of your app?
No. Tester tests over nightly build at every morning which we call BAT(Build Acceptance Test) and verify nightly build.

A Versioning Strategy.
The Web Service Software Factory is now maintained by the community and can be found on the Service Factory site.
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies.
This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.
Retired: November 2011.
Specific tasks must be performed during service development to reduce the complexity of evolving the service. These tasks and design decisions are defined in the following sections and will be augmented with scenarios in a future version of this topic.
XML namespaces qualify XML elements and attributes within a document to prevent them from being interpreted as different nodes with the same local name. Distinct namespaces can be applied to various parts of a Web service, but in most cases only two kinds of namespaces need to be defined: one for the service interface and one for the reusable types defined in the messages.
The namespace defined for the service can be used across all parts of the service (for example, bindings, messages, or interfaces) because these parts usually are not deployed and versioned independently of each other.
Messages are made up of primitive types (for example, strings and integers) and/or custom types. Custom types can be used for only a single operation or they may have been defined as part of an enterprise schema or industry specification to be reused across operations or services. Because reusable types are used with other services, they should have a namespace different from the service interface.
An identifier that represents the major version of the interface and reusable types should be reflected in the namespace. The month and year of the release date is commonly used for this identifier (for example, globalbank/PaymentServices/PaymentTransferService/2006/12).
Messages refer to the payload of data and metadata transferred between a service and consumer. A message can be a request or response and contains primitive and complex types. For more information about messages, see Message Design.
Messages are a common element of services that evolve over time. How they are designed influences how easily they evolve and how useful they are in other contexts. The goal of message design, as it relates to versioning, is to be as explicit as possible for the service description without losing the ability to apply minor revisions in the future.
These goals can be achieved by making all the properties optional for the messages and the complex types the messages contain. To appreciate this design, it is helpful to compare it to other message designs that are used:
Strings and XML . Some messages are defined as a single string and this string may or may not contain XML data. Other messages have one type that is an XML node or element. Both of these designs share common advantages and disadvantages. They are very easy to evolve because there are no expectations on the messages, but because they do not set any expectations, they are useless from a code generation perspective. As a result of this, if a developer-friendly API is desired for the consumer or service, much more work is required. Strong typing . Some messages explicitly define each property and specify whether that value is required. This design results in a robust API for the service and its consumers, but it reduces the flexibility in how the messages evolve.
There are other advantages to this message design. Members of messages and custom types are optional by default in ASMX and WCF. The serialization engine used by ASMX and WCF services also handles optional members correctly.
Message validation is a common requirement for many services. The WSDL document or related XML schemas being used to define messages should not be used to accomplish this validation for reasons defined earlier in Principles and Motivations. Instead, validation should use external documents. These documents may be XML schemas, regular expression statements, or some other document. The validation rules and the means to apply them should be defined in the service's documentation.
Decoupling the validation requirements from the service description also allow each of these items to independently evolve. Changes to the validation requirements may result in a breaking or non-breaking change. The same is true about a change to the service description.
The following tasks should be completed when evolving a service:
Minor revisions : Do not change the XML namespace of the message, type, or service being changed. Do not change the endpoint address of the service. New consumers of this service may be deployed, but the existing consumers will continue to operate as they did before the change. Publish new documentation, metadata, and notifications about the change in accordance with the change control process that has been communicated. Major revisions : Change the XML namespace so it reflects the new release date. Deploy the new service to a test environment so consumers can be tested. Change the endpoint address of the service. Test and deploy the new consumers of the service. Deprecate the old service in a manner and on a timeline in accordance with the change control policy that has been communicated.
Frequently, constraints are placed on a service by some of its consumers. These constraints reduce the flexibility the service has to evolve. In this scenario, the service may choose to evolve differently for different consumers. For this to happen, the consumer must send some form of information that distinguishes the consumer in the message. The service interface then uses this information to dispatch the message to the appropriate implementation.
Another option to increase flexibility involves associating a consumer with the specific operations that consumer will use. This association can take place during the subscription of a service and can be updated at any time. These associations will allow the service to know exactly what consumers will be affected by specific changes to the service.

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. No exceptions.
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> configuração. 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.

No comments:

Post a Comment