it-swarm.cn

如何为多个客户端维护同一软件的不同定制版本

我们有多个具有不同需求的客户。尽管我们的软件在某种程度上实现了模块化,但是几乎可以肯定,我们需要在这里和那里为每个客户调整一些模块的业务逻辑。所做的更改可能太微小了,不足以证明为每个客户将模块拆分为一个单独的(物理)模块是合理的,我担心构建会出现问题,从而造成混乱。但是,这些更改太大且太多,无法通过某些配置文件中的开关进行配置,因为这将导致部署期间出现问题,并且可能会带来很多支持问题,尤其是对于修补程序类型的管理员而言。

我想让构建系统创建多个构建,每个客户端一个,其中更改包含在单个物理模块的专用版本中。所以我有一些问题:

您是否建议让构建系统创建多个构建?如何在源代码管理(尤其是svn)中存储不同的自定义项?

48
Falcon

您需要的是类似功能分支的代码组织。在您的特定情况下,这应称为特定于客户端的中继分支,因为在开发新内容(或解决错误)时,您可能还会使用功能分支)。

http://svnbook.red-bean.com/en/1.5/svn.branchmerge.commonpatterns.html

想法是合并具有新功能分支的代码主干。我的意思是所有非特定于客户的功能。

然后,您还拥有您的特定于客户的分支,在这里您还可以根据需要合并相同功能的分支(如果愿意,可以选择樱桃)。

这些客户端分支的确与功能分支相似,尽管它们不是临时的或短暂的。它们会保留较长的时间,并且主要只合并到其中。应尽可能少地开发特定于客户的功能分支。

特定于客户的分支是中继到中继的并行分支,只要中继本身本身就处于活动状态,甚至在任何地方都不会被合并。

            feature1
            ———————————.
                        \
trunk                    \
================================================== · · ·
      \ client1            \
       `========================================== · · ·
        \ client2            \
         `======================================== · · ·
              \ client2-specific feature   /
               `——————————————————————————´
8
Robert Koritnik

不要对SCM分支执行此操作。使通用代码成为一个单独的项目,该项目会生成库或项目框架工件。每个客户项目都是一个单独的项目,然后依赖于公共项目作为依赖项。

如果您的通用基础应用程序使用像Spring这样的依赖项注入框架,这是最容易的,那么当您需要自定义功能时,您可以轻松地在每个客户项目中注入对象的不同替换变体。即使您还没有DI框架,添加一个框架并以这种方式进行也可能是您最不痛苦的选择。

39
Alb

将所有客户端的软件存储在一个分支中。无需区分为不同客户进行的更改。最随意的是,您希望您的软件对于所有客户端来说都是最好的质量,并且核心基础结构的错误修正应该影响每个人而没有不必要的合并开销,这也可能会引入更多的错误。

模块化通用代码,并实现不同文件中不同的代码或使用不同的定义对其进行保护。使您的构建系统具有针对每个客户端的特定目标,每个目标仅使用与一个客户端相关的代码来编译版本。例如,如果您的代码是C,则可能需要使用“ #ifdef”或您的语言用于构建配置管理的任何机制,并准备一组与客户所付费用的功能相对应的定义。

如果您不喜欢ifdefs,请使用“接口和实现”,“ functors”,“目标文件”或您的语言提供的用于在一个地方存储不同内容的任何工具。

如果您将源分发给客户,则最好使您的构建脚本具有特殊的“源分发目标”。调用此类目标后,它将创建软件sources的特殊版本,将它们复制到单独的文件夹中,以便您可以装运它们,而不进行编译。

13
P Shved

正如许多人所说:适当地分解代码并根据通用代码进行自定义-这将大大提高可维护性。无论您是否使用面向对象的语言/系统,这都是可能的(尽管在C语言中比真正面向对象的东西要难得多)。这正是继承和封装有助于解决的问题类型!

8
Michael Trausch

非常小心

功能分支是一个选项,但我发现它有点繁重。而且,如果不加以控制,则很容易进行深层修改,从而可能导致应用程序完全瘫痪。理想情况下,您希望尽可能增加自定义项,以保持核心代码库尽可能通用和通用。

尽管不知道它是否适用于您的代码库,但无需进行大量修改和重构,这就是我将如何做。我有一个类似的项目,其基本功能是相同的,但每个客户都需要一组非常特定的功能。我创建了一组模块和容器,然后通过配置(IoC)进行组装。

然后,我为每个客户创建了一个项目,该项目主要包含配置和构建脚本,以为其站点创建完全配置的安装。有时我还会放置一些为此客户定制的组件。但这很少见,只要有可能,我都会尝试将其制作为更通用的形式并将其下推,以便其他项目可以使用它们。

最终结果是,我获得了所需的定制级别,获得了定制的安装脚本,因此,当我到达客户站点时,我似乎不会一直在对系统进行周游,并且获得了非常可观的奖励从而能够创建直接挂在构建上的回归测试。这样,无论何时我遇到特定于客户的错误,我都可以编写一个测试,该测试将在部署系统时断言系统,因此即使在该级别也可以进行TDD。

简而言之:

  1. 具有模块化项目结构的高度模块化的系统。
  2. 为每个配置概要文件创建一个项目(客户,尽管可以共享一个概要文件,但可以有多个)
  3. 将所需的功能集组装为其他产品,并按原样对待。

如果做得正确,您的产品部件应包含除少数几个配置文件之外的所有配置文件。

在使用了一段时间后,我最终创建了元软件包,将最常用或必需的系统组装为核心单元,并将此元软件包用于客户装配。几年之后,我最终拥有了一个大型工具箱,可以快速组装以创建客户解决方案。我目前正在研究 Spring Roo ,看看是否无法进一步推广这个想法,希望有一天我可以在我们的第一次采访中与客户一起创建系统的初稿...猜想您可以将其称为“用户驱动开发” ;-)。

希望这对您有所帮助

5
Newtopian

所做的更改可能太微小了,不足以证明为每个客户将模块拆分为一个单独的(物理)模块是合理的,我担心构建会出现问题,从而造成混乱。

海事组织,他们不能太小。如果可能的话,我会在几乎所有地方使用策略模式来分解出特定于客户的代码。这将减少必须分支的代码量,并减少为所有客户端保持通用代码同步所需的合并。它还可以简化测试。您可以使用默认策略来测试常规代码,并分别测试特定于客户端的类。

如果您对模块进行了编码,使得在模块A中使用策略X1要求在模块B中使用策略X2,请考虑进行重构,以便可以将X1和X2合并为一个策略类。

3
kevin cline

如果使用纯C语言编写,这是一种相当丑陋的方法。

  • 通用代码(例如单位“ frangulator.c”)

  • 客户特定的代码,这些代码很小,仅用于每个客户。

  • 在主机代码中,使用#ifdef和#include做类似

#ifdef CLIENT = CLIENTA 
#include“ frangulator_client_a.c” 
#endif 

在需要特定于客户的自定义的所有代码单元中反复使用此模式。

这非常丑陋,并导致其他麻烦,但也很简单,您可以轻松地将客户端特定文件与另一个文件进行交叉比较。

这也意味着所有特定于客户端的片段始终清晰可见(每个片段都在自己的文件中),并且主代码文件与文件中特定于客户端的部分之间存在清晰的关系。

如果您真的很聪明,可以设置makefile来创建正确的客户端定义,例如:

使客户

将为client_a构建,“ make clientb”将为client_b构建,依此类推。

(并且“ make”(没有提供目标)可以发出警告或使用说明。)

我以前也曾使用过类似的想法,虽然设置起来需要一段时间,但它可能非常有效。以我为例,一棵源树构建了大约120种不同的产品。

1
quickly_now

您可以使用SCM维护分支。使主分支保持原始/干净,不受客户端自定义代码的影响。在此分支上进行主要开发。对于应用程序的每个自定义版本,维护单独的分支。任何好的SCM工具都非常适合合并分支(想到Git)。 master分支中的任何更新都应合并到自定义分支中,但是客户端特定的代码可以保留在其自己的分支中。


但是,如果有可能,请尝试以模块化和可配置的方式设计系统。这些自定义分支的不利之处可能在于,它与核心/主服务器之间的距离太远。

1
Htbaa

在git中,我要做的方法是拥有一个包含所有通用代码的master分支,并为每个客户端分支。每当对核心代码进行更改时,只需将所有特定于客户的分支重新置于master之上即可,这样您便拥有了一组适用于当前基准之上的针对客户的移动补丁。

每当您为客户端进行更改时,如果发现应该包含在其他分支中的错误,则可以将其樱桃拣选到master或需要修复的其他分支中(尽管不同的client分支正在共享代码) ,您可能应该使它们都从master分支到一个公共分支)。

0
Cercerilla