Como você organiza software altamente personalizado?

28

Estou trabalhando em um grande projeto de software altamente personalizado para vários clientes em todo o mundo. Isso significa que talvez tenhamos 80% de código comum entre os vários clientes, mas também muitos códigos que precisam ser alterados de um cliente para outro. No passado, fizemos nosso desenvolvimento em repositórios separados (SVN) e, quando um novo projeto foi iniciado (temos poucos, mas grandes clientes), criamos outro repositório com base em qualquer projeto passado que possua a melhor base de código para nossas necessidades. Isso funcionou no passado, mas tivemos vários problemas:

  • Os erros corrigidos em um repositório não são corrigidos em outros repositórios. Isso pode ser um problema de organização, mas acho difícil corrigir e corrigir um bug em 5 repositórios diferentes, tendo em mente que a equipe que mantém esse repositório pode estar em outra parte do mundo e não temos seu ambiente de teste , nem conhece sua programação ou quais requisitos eles têm (um "bug" em um país pode ser um "recurso" em outro).
  • Os recursos e aprimoramentos feitos para um projeto, que também podem ser úteis para outro projeto, são perdidos ou, se usados ​​em outro projeto, muitas vezes causam grandes dores de cabeça, mesclando-os de uma base de código para outra (já que os dois ramos podem ter sido desenvolvidos independentemente por um ano )
  • As refatorações e aprimoramentos de código feitos em uma ramificação de desenvolvimento são perdidos ou causam mais danos do que benefícios, se você precisar mesclar todas essas alterações entre as ramificações.

Agora estamos discutindo como resolver esses problemas e, até agora, surgiram as seguintes idéias sobre como resolver isso:

  1. Mantenha o desenvolvimento em ramificações separadas, mas organize-se melhor com um repositório central onde as correções gerais de erros são mescladas e com todos os projetos que mesclam as alterações desse repositório central em seus próprios, regularmente (por exemplo, diariamente). Isso requer muita disciplina e muito esforço para se fundir entre os ramos. Portanto, não estou convencido de que funcione e que possamos manter essa disciplina, especialmente quando a pressão do tempo se aproxima.

  2. Abandone as ramificações de desenvolvimento separadas e tenha um repositório de código central onde todo o nosso código vive e faça nossa personalização com módulos e opções de configuração conectáveis. Já estamos usando contêineres de injeção de dependência para resolver dependências em nosso código e seguimos o padrão MVVM na maioria de nosso código para separar de maneira limpa a lógica comercial da nossa interface do usuário.

A segunda abordagem parece ser mais elegante, mas temos muitos problemas não resolvidos nessa abordagem. Por exemplo: como lidar com alterações / adições no seu modelo / banco de dados. Estamos usando o .NET com Entity Framework para ter entidades fortemente tipadas. Não vejo como podemos lidar com propriedades que são necessárias para um cliente, mas inúteis para outro cliente sem sobrecarregar nosso modelo de dados. Estamos pensando em resolver isso no banco de dados usando tabelas de satélite (com tabelas separadas em que nossas colunas extras para uma entidade específica vivem com um mapeamento 1: 1 para a entidade original), mas esse é apenas o banco de dados. Como você lida com isso no código? Nosso modelo de dados reside em uma biblioteca central que não poderíamos estender para cada cliente usando essa abordagem.

Tenho certeza de que não somos a única equipe que está lutando com esse problema e estou chocado ao encontrar tão pouco material sobre o assunto.

Então, minhas perguntas são as seguintes:

  1. Que experiência você tem com software altamente personalizado, qual abordagem você escolheu e como funcionou para você?
  2. Que abordagem você recomenda e por quê? Existe uma abordagem melhor?
  3. Existem bons livros ou artigos sobre o assunto que você pode recomendar?
  4. Você tem recomendações específicas para o nosso ambiente técnico (.NET, Entity Framework, WPF, DI)?

Editar:

Obrigado por todas as sugestões. A maioria das idéias corresponde às que já tínhamos em nossa equipe, mas é realmente útil ver a experiência que você teve com elas e dicas para implementá-las melhor.

Ainda não tenho certeza de que caminho seguiremos e não estou tomando a decisão (sozinho), mas transmitirei isso na minha equipe e tenho certeza de que será útil.

No momento, o teor parece ser um único repositório usando vários módulos específicos do cliente. Não tenho certeza de que nossa arquitetura esteja de acordo com isso ou quanto temos que investir para ajustá-la; portanto, algumas coisas podem viver em repositórios separados por um tempo, mas acho que é a única solução de longo prazo que funcionará.

Então, obrigado novamente por todas as respostas!

aKzenT
fonte
Considere tratar tabelas de banco de dados como código.
Já estamos fazendo isso no sentido de que temos nossos scripts de banco de dados em nosso repositório subversion, mas isso realmente não resolve os problemas mencionados acima. Não queremos ter tabelas de estilos de valor-chave em nosso modelo de banco de dados porque elas apresentam muitos problemas. Então, como você permite adições ao seu modelo para clientes individuais e ainda mantém um repositório de código compartilhado para todos eles?
Akzent

Respostas:

10

Parece que o problema fundamental não é apenas a manutenção do repositório de código, mas a falta de arquitetura adequada .

  1. Qual é o núcleo / essência do sistema, que sempre será compartilhado por todos os sistemas?
  2. Quais aprimoramentos / desvios são necessários para cada cliente?

Uma estrutura ou biblioteca padrão abrange a primeira, enquanto a segunda seria implementada como complementos (plugins, subclasses, DI, o que fizer sentido para a estrutura do código).

Um sistema de controle de origem que gerencia filiais e desenvolvimento distribuído provavelmente ajudaria também; Sou fã do Mercurial, outros preferem o Git. A estrutura seria a ramificação principal, cada sistema personalizado seria sub-ramificações, por exemplo.

As tecnologias específicas usadas para implementar o sistema (.NET, WPF, qualquer que seja) são em grande parte sem importância.

Conseguir isso direito não é fácil , mas é fundamental para a viabilidade a longo prazo. E, é claro, quanto mais você esperar, maior será a dívida técnica com a qual terá de lidar.

Você pode achar útil o livro Arquitetura de software: princípios e padrões organizacionais .

Boa sorte!

Steven A. Lowe
fonte
3
Sim. A arquitetura geralmente é mais psicológica do que técnica. Se você se concentrar totalmente no produto, você terminará com uma quebra de trabalho em que os componentes são úteis apenas para esse produto. Se, por outro lado, você se concentrar na criação de um conjunto de bibliotecas que serão mais úteis em geral, construa um conjunto de recursos que podem ser implementados em uma variedade maior de situações. A chave, é claro, é encontrar o equilíbrio certo entre esses dois extremos para sua situação específica.
William Payne
Eu acho que existe uma grande parte compartilhada entre muitas de nossas filiais (> 90%), mas os últimos 10% estão sempre em lugares diferentes; portanto, há muito poucos componentes nos quais não consigo imaginar algumas alterações específicas do cliente, exceto algumas bibliotecas de utilidades que não contém nenhuma lógica comercial.
AkzenT
@aKzenT: hmmm ... não imagine, meça. Pesquise o código e verifique todos os locais em que a personalização ocorreu, faça uma lista dos componentes modificados, observe com que frequência cada componente foi modificado e pense nos tipos e padrões de modificações que realmente foram feitas. Eles são cosméticos ou algorítmicos? Eles estão adicionando ou alterando a funcionalidade básica? Qual o motivo de cada tipo de mudança? Novamente, este é um trabalho árduo , e você pode não gostar das implicações do que descobrir.
9788 Steven A. Lowe
1
Aceitei isso como resposta, porque não podemos tomar essa decisão antes de pensar mais sobre nossa arquitetura, identificar partes que são comuns ou que DEVEM ser comuns e ver se podemos viver com um único repositório ou se é necessário fazer bifurcação (para o momento, pelo menos). Esta resposta reflete essa melhor IMO. Obrigado por todas as outras postagens!
Akzent
11

Uma empresa em que trabalhei tinha o mesmo problema, e a abordagem para resolvê-lo foi a seguinte: Foi criada uma estrutura comum para todos os novos projetos; isso inclui todas as coisas que precisam ser iguais em todos os projetos. Por exemplo, ferramentas de geração de formulários, exportação para Excel, registro em log. Esforço foi feito para garantir que essa estrutura comum fosse aprimorada apenas (quando um novo projeto precisa de novos recursos), mas nunca bifurcada.

Com base nessa estrutura, o código específico do cliente foi mantido em repositórios separados. Quando útil ou necessário, as correções e melhorias são copiadas e coladas entre os projetos (com todas as advertências descritas na pergunta). Melhorias globalmente úteis entram na estrutura comum, no entanto.

Ter tudo em uma base de código comum para todos os clientes tem algumas vantagens, mas, por outro lado, a leitura do código se torna difícil quando existem inúmeros ifs para fazer com que o programa se comporte de maneira diferente para cada cliente.

EDIT: Uma anedota para tornar isso mais compreensível:

O domínio dessa empresa é o gerenciamento de armazém, e uma tarefa de um sistema de gerenciamento de armazém é encontrar um local de armazenamento gratuito para as mercadorias recebidas. Parece fácil, mas na prática, muitas restrições e estratégias precisam ser observadas.

Em um determinado momento, a gerência solicitou que um programador fizesse um módulo flexível e parametrizável para encontrar locais de armazenamento, que implementavam várias estratégias diferentes e deveriam ter sido usados ​​em todos os projetos subseqüentes. O nobre esforço resultou em um módulo complexo, que era muito difícil de entender e manter. No próximo projeto, o líder do projeto não conseguiu descobrir como fazê-lo funcionar naquele armazém, e o desenvolvedor do referido módulo se foi, então ele acabou ignorando-o e escreveu um algoritmo personalizado para essa tarefa.

Alguns anos depois, o layout do armazém onde este módulo foi originalmente usado foi alterado, e o módulo com toda a sua flexibilidade não atendeu aos novos requisitos; então substituí-o por um algoritmo personalizado também.

Sei que o LOC não é uma boa medida, mas de qualquer maneira: o tamanho do módulo "flexível" era ~ 3000 LOC (PL / SQL), enquanto um módulo personalizado para a mesma tarefa leva ~ 100..250 LOC. Portanto, tentar ser flexível aumentou extremamente o tamanho da base de código, sem obter a capacidade de reutilização que esperávamos.

user281377
fonte
Obrigado pelo seu feedback! Essa é uma extensão da primeira solução descrita e também pensamos nisso. No entanto, como a maioria de nossas bibliotecas usa algumas entidades principais e essas entidades principais geralmente são estendidas para um cliente ou outro, acho que poderíamos colocar muito poucas bibliotecas nesse repositório principal. Por exemplo, temos uma entidade "Cliente" definida e o ORM mapeado em uma biblioteca que é usada por quase todas as nossas outras bibliotecas e programas. Mas cada cliente possui alguns campos extras que precisam ser adicionados a um "Cliente", portanto, precisaríamos dividir esta biblioteca e, portanto, todas as bibliotecas, dependendo dela.
precisa saber é o seguinte
3
Nossa idéia para evitar incontáveis ​​"ifs" é fazer uso extensivo da injeção de dependência e trocar módulos completos por diferentes clientes. Não tenho certeza de como isso vai funcionar. Como essa abordagem funcionou para você?
precisa saber é o seguinte
+1, isso corresponde basicamente à minha experiência em projetos que lidaram bem com isso. Se você usar a abordagem 'ifs for different customers', 2 pontos: 1. Não faça if (customer1) ..., faça if (configurationOption1) ... e tenha opções de configuração por cliente. 2. Tente não fazer isso! Talvez 1% do tempo seja a melhor (mais compreensível / facilmente mantenível) opção do que ter módulos específicos de configuração.
vaughandroid
@Baqueta: Só para esclarecer: você recomenda o uso de módulos por cliente em vez de opções de configuração (ifs), certo? Gosto da sua ideia de diferenciar recursos em vez de clientes. Portanto, a combinação de ambos seria ter vários "módulos de recursos", que são controlados pelas opções de configuração. Um cliente é então representado apenas como um conjunto de módulos de recursos independentes. Eu gosto muito dessa abordagem, mas não sei como arquitetar isso. O DI resolve o problema de carregamento e troca de módulos, mas como você gerencia diferentes modelos de dados entre os clientes?
precisa saber é o seguinte
Sim, módulos por recurso e, em seguida, configuração / seleção de recursos por cliente. DI seria o ideal. Infelizmente eu nunca tive que combinar suas necessidades de substancial personalização per-cliente com uma única biblioteca de dados, então eu não tenho certeza se posso ser de muita ajuda lá ...
vaughandroid
5

Um dos projetos nos quais trabalhei deu suporte a várias plataformas (mais de 5) em um grande número de lançamentos de produtos. Muitos dos desafios que você está descrevendo foram coisas que enfrentamos, embora de uma maneira um pouco diferente. Como tínhamos um banco de dados proprietário, não tivemos os mesmos tipos de problemas nessa arena.

Nossa estrutura era semelhante à sua, mas tínhamos um único repositório para o nosso código. O código específico da plataforma foi para as próprias pastas do projeto na árvore de códigos. O código comum residia na árvore com base em qual camada ela pertencia.

Tivemos uma compilação condicional, com base na plataforma que está sendo construída. Manter isso meio doloroso, mas só precisava ser feito quando novos módulos foram adicionados na camada específica da plataforma.

Ter todo o código em um único repositório facilitou a correção de bugs em várias plataformas e lançamentos ao mesmo tempo. Tínhamos um ambiente de compilação automatizado para todas as plataformas servirem de suporte para o caso de um novo código quebrar uma suposta plataforma não relacionada.

Tentamos desencorajá-lo, mas havia casos em que uma plataforma precisava de uma correção com base em um bug específico da plataforma que estava em um código comum. Se pudéssemos substituir condicionalmente a compilação sem fazer com que o módulo parecesse imprudente, faríamos isso primeiro. Caso contrário, removeríamos o módulo do território comum e o empurraríamos para uma plataforma específica.

Para o banco de dados, tínhamos algumas tabelas com colunas / modificações específicas da plataforma. Garantiríamos que todas as versões da tabela da plataforma atendessem a um nível básico de funcionalidade, para que o código comum pudesse fazer referência a ela sem se preocupar com as dependências da plataforma. As consultas / manipulações específicas da plataforma foram enviadas para as camadas do projeto da plataforma.

Então, para responder suas perguntas:

  1. Muitas, e essa foi uma das melhores equipes com as quais trabalhei. A base de código naquela época era de 1 milhão de loc. Não escolhi a abordagem, mas funcionou muito bem. Mesmo em retrospectiva, não vi uma maneira melhor de lidar com as coisas.
  2. Eu recomendo a segunda abordagem que você sugeriu com as nuances mencionadas na minha resposta.
  3. Não há livros em que eu possa pensar, mas eu pesquisaria o desenvolvimento de várias plataformas como iniciante.
  4. Instituir uma governança forte. É a chave para garantir que seus padrões de codificação sejam seguidos. Seguir esses padrões é a única maneira de manter as coisas gerenciáveis ​​e sustentáveis. Tivemos nossa parcela de apelos apaixonados para quebrar o modelo que estávamos seguindo, mas nenhum desses recursos influenciou toda a equipe de desenvolvimento sênior.

fonte
Obrigado por compartilhar sua experiência. Apenas para entender melhor: como é definida uma plataforma no seu caso. Windows, Linux, X86 / x64? Ou algo mais relacionado a diferentes clientes / ambientes? Você está fazendo uma boa observação em 4) Acho que é um dos problemas que temos. Temos uma equipe de muitas pessoas muito inteligentes e qualificadas, mas todo mundo tem idéias um pouco diferentes sobre como fazer as coisas. Sem alguém claramente encarregado da arquitetura, é difícil concordar com uma estratégia comum e você corre o risco de se perder nas discussões sem mudar nada.
precisa saber é o seguinte
@aKzenT - sim, eu quis dizer hardware físico e SO como plataformas. Tínhamos um grande conjunto de recursos, alguns dos quais selecionáveis ​​por um módulo de licenciamento. E apoiamos uma ampla variedade de hardware. Relacionado a isso, tínhamos um diretório de camada comum que possuía vários dispositivos com seus próprios diretórios nessa camada para a árvore de código. Portanto, nossas circunstâncias realmente não estavam tão longe de onde você está. Nossos desenvolvedores seniores teriam estimulado debates entre si, mas, uma vez que uma decisão coletiva foi tomada, todos concordaram em seguir a linha.
4

Trabalhei por muitos anos em um aplicativo de Administração de Pensões que apresentava problemas semelhantes. Os planos de pensão são muito diferentes entre as empresas e exigem conhecimento altamente especializado para implementar lógica e relatórios de cálculo e também design de dados muito diferentes. Só posso dar uma breve descrição de parte da arquitetura, mas talvez ela dê o suficiente da idéia.

Tínhamos duas equipes separadas: uma equipe de desenvolvimento principal, responsável pelo código do sistema principal (que seria seu código compartilhado de 80% acima) e uma equipe de implementação , que possuía experiência em domínio em sistemas de pensão e responsável pelo aprendizado do cliente requisitos e scripts e relatórios de codificação para o cliente.

Tínhamos todas as nossas tabelas definidas por Xml (isso antes do tempo em que estruturas de entidade eram testadas e comuns). A equipe de implementação projetaria todas as tabelas em Xml, e o aplicativo principal poderia ser solicitado a gerar todas as tabelas em Xml. Também havia arquivos de script VB associados, Crystal Reports, documentos do Word etc. para cada cliente. (Também havia um modelo de herança incorporado no Xml para permitir a reutilização de outras implementações).

O aplicativo principal (um aplicativo para todos os clientes), armazenaria em cache todo o material específico do cliente quando chegasse uma solicitação e gerasse um objeto de dados comum (como um conjunto de registros ADO remotos), que poderia ser serializado e transmitido por aí.

Esse modelo de dados é menos liso que os objetos de entidade / domínio, mas é altamente flexível, universal e pode ser processado por um conjunto de códigos principais. Talvez no seu caso, você possa definir seus objetos de entidade base apenas com os campos comuns e ter um Dicionário adicional para campos personalizados (adicione algum tipo de conjunto de descritores de dados ao seu objeto de entidade para que ele possua metadados para os campos personalizados. )

Tínhamos repositórios de origem separados para o código do sistema principal e para o código de implementação.

Na verdade, nosso sistema principal tinha muito pouca lógica de negócios, além de alguns módulos comuns de cálculo comuns. O sistema principal funcionava como: gerador de tela, gerenciador de scripts, gerador de relatórios, acesso a dados e camada de transporte.

Segmentar a lógica principal e a lógica personalizada é um desafio difícil. No entanto, sempre achamos que era melhor ter um sistema principal executando vários clientes, em vez de várias cópias do sistema executando para cada cliente.

Sam Goldberg
fonte
Obrigado pelo feedback. Eu gosto da ideia de ter campos adicionais em um dicionário. Isso nos permitiria ter uma definição única de uma entidade e colocar todas as coisas específicas do cliente em um dicionário. Não sei se existe uma boa maneira de fazê-lo funcionar com nosso wrapper ORM (Entity Framework). E também não tenho certeza se é realmente uma boa ideia ter um modelo de dados compartilhado globalmente em vez de ter um modelo para cada recurso / módulo.
Akzent
2

Trabalhei em um sistema menor (20 kloc) e descobri que o DI e a configuração são ótimas maneiras de gerenciar diferenças entre os clientes, mas não o suficiente para evitar a bifurcação do sistema. O banco de dados é dividido entre uma parte específica do aplicativo, que possui um esquema fixo, e a parte dependente do cliente, definida por meio de um documento de configuração XML personalizado.

Mantivemos uma única filial no mercurial configurada como se fosse entregável, mas com marca e configurada para um cliente fictício. As correções de erros são incorporadas nesse projeto, e o novo desenvolvimento da funcionalidade principal acontece apenas lá. As liberações para clientes reais são ramificações disso, armazenadas em seus próprios repositórios. Nós acompanhamos grandes alterações no código por meio de números de versão escritos manualmente e acompanhamos correções de erros usando números de confirmação.

Dan Monego
fonte
você diferenciou as bibliotecas principais que são as mesmas entre os clientes ou você bifurca a árvore completa? Você mescla regularmente da linha principal aos garfos individuais? E se sim, quanto tempo a rotina diária mesclou e como você evitou que esse procedimento desmoronasse quando a pressão do tempo começa (como sempre ocorre em um ponto do projeto)? Desculpe por tantas perguntas: p
aKzenT
Nós bifurcamos a árvore inteira, principalmente porque as solicitações do cliente vêm antes da criação da integridade do sistema. A fusão do sistema principal ocorre manualmente usando ferramentas mercuriais e outras, e geralmente é limitada a erros críticos ou a grandes atualizações de recursos. Tentamos manter as atualizações de grandes blocos pouco frequentes, devido ao custo da mesclagem e porque muitos clientes hospedam seus próprios sistemas e não queremos colocar custos de instalação neles sem fornecer valor.
Dan Monego
Estou curioso: o IIRC mercurial é um DVCS semelhante ao git. Você notou alguma vantagem em fazer essas fusões entre filiais em comparação com o Subversion ou outro VCS tradicional? Acabei de terminar um processo de mesclagem muito doloroso entre dois ramos desenvolvidos completamente separados usando o subversion e estava pensando se seria mais fácil se usássemos o git.
precisa saber é o seguinte
A fusão com o mercurial tem sido muito, muito mais fácil do que com a nossa ferramenta anterior, que era o Vault. Uma das principais vantagens é que o mercurial é realmente bom em colocar o histórico de alterações na frente e no centro do aplicativo, o que facilita o rastreamento do que foi feito onde. A parte mais difícil para nós foi mudar as ramificações existentes - se você estiver mesclando duas ramificações, o mercurial exige que ambas tenham a mesma raiz, portanto, para obter essa configuração, foi necessária uma última mesclagem completamente manual.
Dan Monego
2

Receio não ter experiência direta com o problema que você descreve, mas tenho alguns comentários.

A segunda opção, reunir o código em um repositório central (tanto quanto possível) e arquitetar para personalização (novamente, tanto quanto possível) é quase certamente o caminho a longo prazo.

O problema é como você planeja chegar lá e quanto tempo levará.

Nessa situação, provavelmente não há problema em (temporariamente) ter mais de uma cópia do aplicativo no repositório por vez.

Isso permitirá que você mude gradualmente para uma arquitetura que suporte diretamente a personalização sem precisar fazê-lo de uma só vez.

William Payne
fonte
2

A segunda abordagem parece ser mais elegante, mas temos muitos problemas não resolvidos nessa abordagem.

Estou certo de que qualquer um desses problemas pode ser resolvido, um após o outro. Se você ficar preso, pergunte aqui no SO sobre o problema específico.

Como outros já apontaram, ter uma base de código central / um repositório é a opção que você prefere. Eu tento responder a sua pergunta de exemplo.

Por exemplo: como lidar com alterações / adições no seu modelo / banco de dados. Estamos usando o .NET com Entity Framework para ter entidades fortemente tipadas. Não vejo como podemos lidar com propriedades que são necessárias para um cliente, mas inúteis para outro cliente sem sobrecarregar nosso modelo de dados.

Existem algumas possibilidades, todas elas que eu já vi em sistemas do mundo real. Qual escolher depende da sua situação:

  • conviver com a desordem até certo ponto
  • introduza as tabelas "CustomAttributes" (descrevendo nomes e tipos) e "CustomAttributeValues" (para os valores, por exemplo, armazenados como uma representação de sequência, mesmo que sejam números). Isso permitirá adicionar esses atributos no momento da instalação ou no tempo de execução, tendo valores individuais para cada cliente. Não insista em que cada atributo personalizado seja modelado "visivelmente" em seu modelo de dados.

  • agora deve ficar claro como usar isso no código: tenha apenas código geral para acessar essas tabelas e código individual (talvez em uma DLL de plug-in separada, que é sua) para interpretar esses atributos corretamente

  • outra alternativa é fornecer a cada tabela de entidades um grande campo de strings onde você pode adicionar um string XML individual.
  • tente generalizar alguns conceitos, para que possam ser reutilizados com mais facilidade em diferentes clientes. Eu recomendo o livro de Martin Fowler " Analysis patterns ". Embora este livro não seja sobre a personalização de software em si, também pode ser útil para você.

E para código específico: você também pode tentar introduzir uma linguagem de script em seu produto, especialmente para adicionar scripts específicos ao cliente. Dessa forma, você não apenas cria uma linha clara entre seu código e o código específico do cliente, mas também pode permitir que seus clientes personalizem o sistema até certo ponto por si próprios.

Doc Brown
fonte
Os problemas que vejo ao adicionar colunas CustomAttributes ou XML para armazenar propriedades personalizadas são que eles são muito limitados em suas habilidades. Por exemplo, classificar, agrupar ou filtrar com base nesses atributos é muito desafiador. Certa vez, trabalhei em um sistema que usava uma tabela de atributos extras e fica cada vez mais complexo manter e manipular esses atributos personalizados. Por essas razões, eu estava pensando em vez de colocar esses atributos como colunas em uma tabela extra que é mapeada 1: 1 para o original. O problema de como definir, consultar e gerenciar estes ainda é o mesmo.
Akzent
@aKzenT: a personalização não é gratuita, você terá que trocar a facilidade de uso por uma personalização. Minha sugestão geral é que você não introduza dependências nas quais a parte principal do sistema dependa de alguma forma de qualquer parte personalizada, apenas o contrário. Por exemplo, ao introduzir essas "tabelas extras" para o cliente 1, você pode evitar implantar essas tabelas e o código relacionado para o cliente 2? Se a resposta for sim, então a solução está ok.
Doc Brown
0

Eu apenas criei um desses aplicativos. Eu diria que 90% das unidades vendidas foram vendidas como estão, sem modificações. Cada cliente tinha sua própria capa personalizada e atendemos o sistema nessa capa. Quando um mod chegou e afetou as seções principais, tentamos usar a ramificação IF . Quando o mod # 2 entrou na mesma seção, mudamos para a lógica CASE, que permitia uma expansão futura. Isso parecia lidar com a maioria dos pedidos menores.

Quaisquer pedidos personalizados menores adicionais foram tratados implementando a lógica de Caso.

Se os mods fossem dois radicais, construímos um clone (inclusão separada) e envolvemos um CASE em torno dele para incluir o módulo diferente.

As correções e modificações no núcleo afetavam todos os usuários. Testamos minuciosamente o desenvolvimento antes de iniciar a produção. Sempre enviamos notificações por e-mail que acompanharam qualquer alteração e NUNCA, NUNCA, NUNCA postou alterações de produção às sextas-feiras ... NUNCA.

Nosso ambiente era o ASP clássico e o SQL Server. Nós não éramos uma loja de códigos de espaguete ... Tudo era modular usando Inclui, Sub-rotinas e Funções.

Michael Riley - também conhecido por Gunny
fonte
-1

Quando me pedem para iniciar o desenvolvimento de B, que está compartilhando 80% da funcionalidade com A, eu:

  1. Clone A e modifique-o.
  2. Extraia a funcionalidade que A e B compartilham em C que eles usarão.
  3. Torne A configurável o suficiente para atender às necessidades de B e de si mesmo (portanto, B está incorporado em A).

Você escolheu 1, e isso não parece se encaixar bem na sua situação. Sua missão é prever qual dos 2 e 3 é o mais adequado.

Moshe Revah
fonte
1
Parece fácil, mas como fazer isso na prática? Como você torna seu software tão configurável sem sobrecarregá-lo com "if (customer1)", que se torna impossível de manter após algum tempo.
Akzent
@aKzenT É por isso que deixei 2 e 3 para você escolher. Se o tipo de mudanças necessárias para necessidades make de cliente1 projecto de apoio do customer2 através de configuração vai tornar o código insustentável então não faça 3.
Moshe Revah
Eu prefiro fazer "if (option1)" em vez de "if (customer1)". Dessa forma, com N opções, posso gerenciar muitos possíveis clientes. Por exemplo, com N opções booleanas, você pode gerenciar 2 ^ N clientes, tendo apenas N 'se' ... impossível gerenciar com a estratégia "se (cliente1)" que exigiria 2 ^ N 'se'.
Fil