Como manter diferentes versões personalizadas do mesmo software para vários clientes

43

temos vários clientes com necessidades diferentes. Embora nosso software seja modularizado até certo ponto, é quase certo que precisamos ajustar um pouco a lógica de negócios de cada módulo aqui e ali para cada cliente. As mudanças são provavelmente muito pequenas para justificar a divisão do módulo em um módulo distinto (físico) para cada cliente, eu temo problemas com a construção, um caos de ligação. No entanto, essas alterações são muito grandes e muitas para configurá-las por comutadores em algum arquivo de configuração, pois isso levaria a problemas durante a implantação e provavelmente a muitos problemas de suporte, especialmente com administradores do tipo consertador.

Eu gostaria que o sistema de compilação criava várias compilações, uma para cada cliente, em que as alterações estão contidas em uma versão especializada do único módulo físico em questão. Então tenho algumas perguntas:

Você aconselharia deixar o sistema de criação criar várias construções? Como devo armazenar as diferentes personalizações no controle de origem, especialmente no svn?

    
por Falcon 21.03.2011 / 11:21
fonte

9 respostas

8

O que você precisa é de organização de código semelhante a ramificação . Em seu cenário específico, isso deve ser chamado de ramificação de tronco específica ao cliente , pois é provável que você use ramificações de recursos também enquanto desenvolve coisas novas (ou resolve bugs).

link

A ideia é ter um tronco de código com as ramificações dos novos recursos incorporadas. Quero dizer, todos os recursos não específicos do cliente.

Em seguida, você também tem suas ramificações específicas do cliente , nas quais também mescla as ramificações dos mesmos recursos, conforme necessário (escolha se desejar).

Essas ramificações de cliente parecem semelhantes a ramificações de recursos, embora não sejam temporárias ou de curta duração. Eles são mantidos por um longo período de tempo e eles são principalmente unidos em. Deve haver o menor desenvolvimento possível de ramificações de recursos específicos do cliente.

As ramificações específicas do cliente são ramificações paralelas para o tronco e estão sendo ativadas enquanto o próprio tronco não é mesclado como um todo em qualquer lugar.

            feature1
            ———————————.
                        \
trunk                    \
================================================== · · ·
      \ client1            \
       '========================================== · · ·
        \ client2            \
         '======================================== · · ·
              \ client2-specific feature   /
               '——————————————————————————´
    
por 21.03.2011 / 11:37
fonte
33

Não faça isso com ramificações de SCM. Torne o código comum um projeto separado que produz uma biblioteca ou um artefato de esqueleto do projeto. Cada projeto de cliente é um projeto separado que depende do comum como dependência.

Isso é mais fácil se o aplicativo base comum usar uma estrutura de injeção de dependência como o Spring, para que você possa injetar facilmente diferentes variantes de substituição de objetos em cada projeto do cliente, pois recursos personalizados são necessários. Mesmo que você ainda não tenha uma estrutura de DI, adicionar uma e fazer dessa maneira pode ser sua escolha menos dolorosa.

    
por 22.03.2011 / 00:51
fonte
9

Armazene o software para todos os clientes em um único ramo. Não há necessidade de divergir as alterações feitas para diferentes clientes. A maioria das vezes, você vai querer que seu software seja de melhor qualidade para todos os clientes, e a correção de bugs para sua infra-estrutura básica deve afetar todos, sem sobrecarga desnecessária, que também pode introduzir mais bugs.

Modularize o código comum e implemente o código que difere em arquivos diferentes ou proteja-o com diferentes definições. Faça com que seu sistema de compilação tenha destinos específicos para cada cliente, cada destino compilando uma versão com apenas o código relacionado a um cliente. Se o seu código for C, por exemplo, talvez você queira proteger recursos para clientes diferentes com " #ifdef " ou qualquer mecanismo que seu idioma tenha para o gerenciamento de configuração de construção e preparar um conjunto de definições correspondentes à quantidade de recursos que um cliente possui pago por.

Se você não gosta de ifdef s, use "interfaces e implementações", "functores", "arquivos de objetos" ou qualquer ferramenta que sua linguagem forneça para armazenar coisas diferentes em um só lugar.

Se você distribuir fontes para seus clientes, é melhor fazer com que seus scripts de construção tenham "destinos de distribuição de origem" especiais. Uma vez que você invocar tal alvo, ele cria uma versão especial de fontes do seu software, copia-os para uma pasta separada para que você possa enviá-los e não compilá-los.

    
por 21.03.2011 / 22:25
fonte
7

Como muitos afirmaram: fatore seu código corretamente e personalize com base no código comum - isso melhorará significativamente a capacidade de manutenção. Quer você esteja usando uma linguagem / sistema orientada a objetos ou não, isso é possível (embora seja um pouco mais difícil de fazer em C do que algo que é verdadeiramente orientado a objetos). Este é precisamente o tipo de problema que a herança e o encapsulamento ajudam a resolver!

    
por 22.03.2011 / 01:04
fonte
4

Muito cuidadosamente

Recurso Ramificação é uma opção, mas eu acho que é um pouco pesado. Também facilita as modificações profundas, o que pode levar a uma bifurcação imediata de sua aplicação, se não for mantida sob controle. Idealmente, você quer aumentar o máximo possível as personalizações, na tentativa de manter sua base de código básica o mais comum e genérica possível.

Aqui está como eu faria isso, embora eu não saiba se é aplicável à sua base de código sem modificações pesadas e re-factorings. Eu tinha um projeto semelhante em que a funcionalidade básica era a mesma, mas cada cliente exigia um conjunto muito específico de recursos. Eu criei um conjunto de módulos e contêineres que eu monto através da configuração (à la IoC).

em seguida, para cada cliente, criei um projeto que basicamente contém as configurações e o script de construção para criar uma instalação totalmente configurada para o site deles. Ocasionalmente eu coloco também alguns componentes customizados para este cliente. Mas isso é raro e, sempre que possível, tento torná-lo mais genérico e empurrado para baixo, para que outros projetos possam usá-lo.

O resultado final é que eu tenho o nível de personalização que eu precisava, eu tenho scripts de instalação personalizados para que quando eu chegar ao site do cliente eu não pareça que estou tweeking o sistema o tempo todo e como um adicional MUITO significativo bônus eu conseguir ser capaz de criar testes de regressão ligados diretamente na compilação. Dessa forma, sempre que recebo um bug específico do cliente, posso escrever um teste que afirma que o sistema é implantado e, portanto, pode fazer o TDD mesmo nesse nível.

então, resumindo:

  1. Sistema altamente modularizado com uma estrutura de projeto plana.
  2. Crie um projeto para cada perfil de configuração (cliente, embora mais de um possa compartilhar um perfil)
  3. Monte os conjuntos de funcionalidades necessários como um produto diferente e trate-o como tal.

Se feito corretamente, a montagem do produto deve conter todos, exceto alguns arquivos de configuração.

Depois de usar isso por um tempo, acabei criando meta-pacotes que montam os sistemas mais usados ou essenciais como uma unidade central e uso este meta-pacote para montagens de clientes. Depois de alguns anos, acabei tendo uma grande caixa de ferramentas que eu poderia montar muito rapidamente para criar soluções para o cliente. No momento, estou investigando o Spring Roo e ver se não consigo levar a ideia um pouco adiante, esperando que um dia eu possa criar um primeiro rascunho do sistema certo com o cliente em nossa primeira entrevista ... Eu acho que você poderia chamar de Desenvolvimento Orientado pelo Usuário; -).

Espero que isso tenha ajudado

    
por 22.03.2011 / 03:34
fonte
3

The changes are probably too tiny to justify splitting the module into a distinct (physical) module for each client, I fear problems with the build, a linking chaos.

IMO, eles não podem ser muito pequenos. Se possível, eu fatoraria o código específico do cliente usando o padrão de estratégia praticamente em todos os lugares. Isso reduzirá a quantidade de código que deve ser ramificada e reduzirá a mesclagem necessária para manter o código geral em sincronia para todos os clientes. Ele também simplificará o teste ... você poderá testar o código geral usando estratégias padrão e testar as classes específicas do cliente separadamente.

Se seus módulos são codificados de forma que usar a política X1 no módulo A requer o uso da política X2 no módulo B, pense na refatoração para que X1 e X2 possam ser combinados em uma única classe de política.

    
por 21.03.2011 / 20:28
fonte
1

Você pode usar seu SCM para manter filiais. Mantenha a ramificação mestre como nova / limpa a partir do código customizado do cliente. Faça o desenvolvimento principal neste ramo. Para cada versão personalizada do aplicativo, mantenha ramificações separadas. Qualquer boa ferramenta SCM irá se dar muito bem com as ramificações de mesclagem (o Git vem à mente). Quaisquer atualizações na ramificação principal devem ser mescladas nas ramificações personalizadas, mas o código específico do cliente pode permanecer em sua própria ramificação.

Embora, se possível, tente projetar o sistema de maneira modular e configurável. A desvantagem desses ramos personalizados pode ser que ele se mova muito para longe do núcleo / mestre.

    
por 21.03.2011 / 11:36
fonte
1

Se você está escrevendo em linguagem C, aqui está uma maneira bem feia de fazer isso.

  • Código comum (por exemplo, unidade "frangulator.c")

  • código específico do cliente, peças pequenas e usadas apenas para cada cliente.

  • no código da unidade principal, use #ifdef e #include para fazer algo como

#ifdef CLIENT=CLIENTA
#include "frangulator_client_a.c"
#endif

Use isso como um padrão repetidamente em todas as unidades de código que exigem personalização específica do cliente.

Isso é muito feio e leva a alguns outros problemas, mas também é simples, e você pode comparar os arquivos específicos de um cliente com o outro facilmente.

Isso também significa que todas as partes específicas do cliente são claramente visíveis (cada uma no próprio arquivo) em todos os momentos, e há uma relação clara entre o arquivo de código principal e a parte específica do cliente do arquivo.

Se você for realmente esperto, você pode configurar makefiles para criar a definição correta do cliente, assim:

tornar clienta

será criado para client_a, e "make clientb" será responsável por client_b e assim por diante.

(e "make" sem nenhum alvo fornecido pode emitir um aviso ou uma descrição de uso.)

Já usei uma ideia semelhante, demora um pouco para configurar, mas pode ser muito eficaz. No meu caso, uma árvore de fontes construiu cerca de 120 produtos diferentes.

    
por 22.03.2011 / 02:15
fonte
0

No git, o jeito que eu faço é ter um branch master com todo o código comum, e branches para cada cliente. Sempre que uma alteração no código principal é feita, apenas rebaixe todas as ramificações específicas do cliente na parte superior do mestre, para que você tenha um conjunto de correções móveis para os clientes que são aplicados sobre a linha de base atual.

Sempre que você está fazendo uma mudança para um cliente e percebe um bug que deve ser incluído em outras ramificações, você pode escolher isso para o mestre ou para as outras ramificações que precisam de correção (embora de ramificações de cliente diferentes estão compartilhando código, você provavelmente deve ter ambos ramificando-se de um ramo comum do mestre).

    
por 21.03.2011 / 23:30
fonte