如何为每个客户管理同一软件的多个版本?

时间:2022-10-19 23:28:55

I have source code that is 95% the same for all customers. Some customers ask for something specific, however. How can I manage this, is it possible with VisualSVN/Subversion?

我的源代码对所有客户都是95%相同。然而,有些客户要求具体的东西。我如何管理这个,是否可以使用VisualSVN / Subversion?

Update:

Some details about the application, it's a web ASP.NET MVC with NHibernate.

关于应用程序的一些细节,它是一个带有NHibernate的Web ASP.NET MVC。

The application has several projects : the web part, the repo part (where we use NHibernate to access database) and a service project.

该应用程序有几个项目:Web部分,repo部分(我们使用NHibernate访问数据库)和服务项目。

The service project uses the repo project and the service project is the project with business rules.

服务项目使用repo项目,服务项目是具有业务规则的项目。

7 个解决方案

#1


11  

I can think of two approaches that might work.

我可以想到两种可行的方法。

The first involves branching the code for each customer. Any edit you make in the main line can then be integrated into the specific customer's branch when it's needed. Similarly if something in the core product is fixed in a branch it can be merged back into the main line for subsequent propagation to the other customers' branches. While this might seem the best approach it can be hard to maintain and keeping track of which branch has which edits will get fraught.

第一个涉及为每个客户分支代码。然后,在主线上​​进行的任何编辑都可以在需要时集成到特定客户的分支中。类似地,如果核心产品中的某些东西在分支中被修复,它可以合并回主线,以便随后传播到其他客户的分支。虽然这看起来似乎是最好的方法,但是很难维护和跟踪哪个分支会受到哪些编辑的影响。

The second, and perhaps better approach, involves refactoring your code so that the customer specific code is in a single assembly - one per customer. This is then configured, perhaps by using dependency injection, when the product is installed. This way you only have one code line and no merging between branches. Though it does rely on the customer specific code being easily separated out.

第二种也许是更好的方法涉及重构代码,以便客户特定代码在一个程序集中 - 每个客户一个。然后,在安装产品时,可能通过使用依赖项注入来配置它。这样,您只有一个代码行,并且分支之间没有合并。虽然它确实依赖于客户特定的代码很容易分离出来。

#2


5  

Place the customer specific code in separate projects/assemblies. Something like the strategy pattern or plug-ins might be the way to go.

将客户特定代码放在单独的项目/程序集中。像战略模式或插件这样的东西可能是要走的路。

The other less attractive way (IMO) would be to create separate branches for each customer but this can quickly become hard to maintain.

另一个不太吸引人的方式(IMO)将为每个客户创建单独的分支,但这很快就会变得难以维护。

#3


5  

The approach we have taken is:

我们采取的方法是:

  • Insert hooks inside the application allowing the default behaviour to be customized (e.g. when an Save action is called the first thing that happens inside is a call to OnSaveHandler).
  • 在应用程序中插入钩子,允许自定义默认行为(例如,当调用Save动作时,内部发生的第一件事就是调用OnSaveHandler)。
  • The default handler does not do anything, it just returns "continueWithNormalExecution". All the handlers are in a different module than the original application (different assembly), let's call it BehaviourModule
  • 默认处理程序不执行任何操作,只返回“continueWithNormalExecution”。所有处理程序都与原始应用程序(不同的程序集)位于不同的模块中,我们称之为BehaviourModule
  • On client based requests we modify this BehaviourModule by overriding the default 'don't do anything behavior'. The return code of this modified handler can be: ContinueNormalExecution, SkipNormalExecution, TerminateExecution, etc ...
  • 在基于客户端的请求中,我们通过覆盖默认的“不做任何行为”来修改此BehaviourModule。这个修改过的处理程序的返回码可以是:ContinueNormalExecution,SkipNormalExecution,TerminateExecution等......
  • In other cases we insert hooks based on interfaces. In the BehaviourModule we will have more handlers implementing this interface, e.g. DoStuffInterface, the BehaviourModule is parsed at load time using reflection and all handlers implementing DoStuffInterface will be register in the system. Then in the original application we will have something like: If GetDoStuffInterfaceHandler(handlerID) isnot Nothing then GetDoStuffInterfaceHandler(handlerID).DoStuff(). Defining which handlerId to use is configurable (could be through a db table, xml file ,etc).

    在其他情况下,我们插入基于接口的钩子。在BehaviourModule中,我们将有更多处理程序实现此接口,例如在DoStuffInterface中,使用反射在加载时解析BehaviourModule,并且所有实现DoStuffInterface的处理程序都将在系统中注册。然后在原始应用程序中我们会有类似的东西:如果GetDoStuffInterfaceHandler(handlerID)不是Nothing那么GetDoStuffInterfaceHandler(handlerID).DoStuff()。定义要使用的handlerId是可配置的(可以通过db表,xml文件等)。

    We end up having multiple handlers implementing DoStuffInterface with different IDs and calling them at different times.

    我们最终有多个处理程序使用不同的ID实现DoStuffInterface并在不同的时间调用它们。

With this approach we have:

通过这种方法,我们有:

  • the basic application with the default behaviour
  • 具有默认行为的基本应用程序
  • a configurable module (assembly) the customizes the way the application works
  • 可配置模块(程序集)自定义应用程序的工作方式

The challenge with this approach is finding the "sweet points" - behaviours that the client might want to customize and inserting hooks there.

这种方法的挑战是找到“甜点” - 客户端可能想要自定义的行为并在那里插入钩子。

Hope I was clear in my description, if not... leave a comment :)

希望我的描述清楚,如果不是......发表评论:)

#4


1  

If it is no big deal, i would go with appp setting and factory pattern. Or specific assemblies per customer.

如果没什么大不了的,我会选择appp设置和工厂模式。或每个客户的特定组件。

But from tags it looks you want to solve it via version control. But this will put a big hit on merging etc. You will have to create branch for each customer and merge changes from trunk to them.

但是从标签看起来你想要通过版本控制解决它。但这将对合并等产生重大影响。您必须为每个客户创建分支,并将更改从主干合并到它们。

#5


1  

A useful adjunct to #ifdef ACME/#endif etc. is to define macros for ACME_ONLY(), NON_ACME(), FROBOZCO_ONLY(), NON_FROBOZCO(), etc. macros. Stuff can still get messy if new versions come into play (in which cases should the new version behave like Acme, FrobozCo, etc.) but if there's only one line of difference between the Acme and non-Acme version, this approach avoids surrounding that line by two lines of #directives.

#ifdef ACME /#endif等的有用附件是为ACME_ONLY(),NON_ACME(),FROBOZCO_ONLY(),NON_FROBOZCO()等宏定义宏。如果新版本发挥作用,东西仍然会变得混乱(在这种情况下,新版本的行为应该像Acme,FrobozCo等)但是如果Acme和非Acme版本之间只有一条区别,那么这种方法可以避免由两行#directives排成一行。

#6


0  

The difference of 5% is that only UI based or also business logic? If UI based than you should sperate the UI layer and ship/compile the appropiate UI file with the application. If business logic, this is more complicated. Maybe branching (via SVN) could help out. But still a hassle with current development to the application, therefore not advised.

5%的差异是只有基于UI还是业务逻辑?如果基于UI,则应该对UI层进行操作,并使用应用程序发送/编译适当的UI文件。如果是业务逻辑,这就更复杂了。也许分支(通过SVN)可以提供帮助。但是对于应用程序的当前开发仍然是一个麻烦,因此不建议。

#7


0  

Using version control to solve this problem is probably going to cause more problems than it solves.

使用版本控制来解决这个问题可能会导致比它解决的问题更多的问题。

Suggestions by others to separate the client specific code into separate assemblies and/or use dependency injection is one way.

其他人建议将客户特定代码分成单独的程序集和/或使用依赖注入是一种方法。

Another option is to use #if ... #endif.

另一个选择是使用#if ... #endif。

#if CustomerA

    ... do x ...

#else

    ... do y ...

#endif

You'll need to adjust your build scripts to build the specific customer binaries. eg:

您需要调整构建脚本以构建特定的客户二进制文件。例如:

msbuild mysolution.sln /property:DefineConstants="CustomerA"

msbuild mysolution.sln /property:DefineConstants="CustomerB"

#1


11  

I can think of two approaches that might work.

我可以想到两种可行的方法。

The first involves branching the code for each customer. Any edit you make in the main line can then be integrated into the specific customer's branch when it's needed. Similarly if something in the core product is fixed in a branch it can be merged back into the main line for subsequent propagation to the other customers' branches. While this might seem the best approach it can be hard to maintain and keeping track of which branch has which edits will get fraught.

第一个涉及为每个客户分支代码。然后,在主线上​​进行的任何编辑都可以在需要时集成到特定客户的分支中。类似地,如果核心产品中的某些东西在分支中被修复,它可以合并回主线,以便随后传播到其他客户的分支。虽然这看起来似乎是最好的方法,但是很难维护和跟踪哪个分支会受到哪些编辑的影响。

The second, and perhaps better approach, involves refactoring your code so that the customer specific code is in a single assembly - one per customer. This is then configured, perhaps by using dependency injection, when the product is installed. This way you only have one code line and no merging between branches. Though it does rely on the customer specific code being easily separated out.

第二种也许是更好的方法涉及重构代码,以便客户特定代码在一个程序集中 - 每个客户一个。然后,在安装产品时,可能通过使用依赖项注入来配置它。这样,您只有一个代码行,并且分支之间没有合并。虽然它确实依赖于客户特定的代码很容易分离出来。

#2


5  

Place the customer specific code in separate projects/assemblies. Something like the strategy pattern or plug-ins might be the way to go.

将客户特定代码放在单独的项目/程序集中。像战略模式或插件这样的东西可能是要走的路。

The other less attractive way (IMO) would be to create separate branches for each customer but this can quickly become hard to maintain.

另一个不太吸引人的方式(IMO)将为每个客户创建单独的分支,但这很快就会变得难以维护。

#3


5  

The approach we have taken is:

我们采取的方法是:

  • Insert hooks inside the application allowing the default behaviour to be customized (e.g. when an Save action is called the first thing that happens inside is a call to OnSaveHandler).
  • 在应用程序中插入钩子,允许自定义默认行为(例如,当调用Save动作时,内部发生的第一件事就是调用OnSaveHandler)。
  • The default handler does not do anything, it just returns "continueWithNormalExecution". All the handlers are in a different module than the original application (different assembly), let's call it BehaviourModule
  • 默认处理程序不执行任何操作,只返回“continueWithNormalExecution”。所有处理程序都与原始应用程序(不同的程序集)位于不同的模块中,我们称之为BehaviourModule
  • On client based requests we modify this BehaviourModule by overriding the default 'don't do anything behavior'. The return code of this modified handler can be: ContinueNormalExecution, SkipNormalExecution, TerminateExecution, etc ...
  • 在基于客户端的请求中,我们通过覆盖默认的“不做任何行为”来修改此BehaviourModule。这个修改过的处理程序的返回码可以是:ContinueNormalExecution,SkipNormalExecution,TerminateExecution等......
  • In other cases we insert hooks based on interfaces. In the BehaviourModule we will have more handlers implementing this interface, e.g. DoStuffInterface, the BehaviourModule is parsed at load time using reflection and all handlers implementing DoStuffInterface will be register in the system. Then in the original application we will have something like: If GetDoStuffInterfaceHandler(handlerID) isnot Nothing then GetDoStuffInterfaceHandler(handlerID).DoStuff(). Defining which handlerId to use is configurable (could be through a db table, xml file ,etc).

    在其他情况下,我们插入基于接口的钩子。在BehaviourModule中,我们将有更多处理程序实现此接口,例如在DoStuffInterface中,使用反射在加载时解析BehaviourModule,并且所有实现DoStuffInterface的处理程序都将在系统中注册。然后在原始应用程序中我们会有类似的东西:如果GetDoStuffInterfaceHandler(handlerID)不是Nothing那么GetDoStuffInterfaceHandler(handlerID).DoStuff()。定义要使用的handlerId是可配置的(可以通过db表,xml文件等)。

    We end up having multiple handlers implementing DoStuffInterface with different IDs and calling them at different times.

    我们最终有多个处理程序使用不同的ID实现DoStuffInterface并在不同的时间调用它们。

With this approach we have:

通过这种方法,我们有:

  • the basic application with the default behaviour
  • 具有默认行为的基本应用程序
  • a configurable module (assembly) the customizes the way the application works
  • 可配置模块(程序集)自定义应用程序的工作方式

The challenge with this approach is finding the "sweet points" - behaviours that the client might want to customize and inserting hooks there.

这种方法的挑战是找到“甜点” - 客户端可能想要自定义的行为并在那里插入钩子。

Hope I was clear in my description, if not... leave a comment :)

希望我的描述清楚,如果不是......发表评论:)

#4


1  

If it is no big deal, i would go with appp setting and factory pattern. Or specific assemblies per customer.

如果没什么大不了的,我会选择appp设置和工厂模式。或每个客户的特定组件。

But from tags it looks you want to solve it via version control. But this will put a big hit on merging etc. You will have to create branch for each customer and merge changes from trunk to them.

但是从标签看起来你想要通过版本控制解决它。但这将对合并等产生重大影响。您必须为每个客户创建分支,并将更改从主干合并到它们。

#5


1  

A useful adjunct to #ifdef ACME/#endif etc. is to define macros for ACME_ONLY(), NON_ACME(), FROBOZCO_ONLY(), NON_FROBOZCO(), etc. macros. Stuff can still get messy if new versions come into play (in which cases should the new version behave like Acme, FrobozCo, etc.) but if there's only one line of difference between the Acme and non-Acme version, this approach avoids surrounding that line by two lines of #directives.

#ifdef ACME /#endif等的有用附件是为ACME_ONLY(),NON_ACME(),FROBOZCO_ONLY(),NON_FROBOZCO()等宏定义宏。如果新版本发挥作用,东西仍然会变得混乱(在这种情况下,新版本的行为应该像Acme,FrobozCo等)但是如果Acme和非Acme版本之间只有一条区别,那么这种方法可以避免由两行#directives排成一行。

#6


0  

The difference of 5% is that only UI based or also business logic? If UI based than you should sperate the UI layer and ship/compile the appropiate UI file with the application. If business logic, this is more complicated. Maybe branching (via SVN) could help out. But still a hassle with current development to the application, therefore not advised.

5%的差异是只有基于UI还是业务逻辑?如果基于UI,则应该对UI层进行操作,并使用应用程序发送/编译适当的UI文件。如果是业务逻辑,这就更复杂了。也许分支(通过SVN)可以提供帮助。但是对于应用程序的当前开发仍然是一个麻烦,因此不建议。

#7


0  

Using version control to solve this problem is probably going to cause more problems than it solves.

使用版本控制来解决这个问题可能会导致比它解决的问题更多的问题。

Suggestions by others to separate the client specific code into separate assemblies and/or use dependency injection is one way.

其他人建议将客户特定代码分成单独的程序集和/或使用依赖注入是一种方法。

Another option is to use #if ... #endif.

另一个选择是使用#if ... #endif。

#if CustomerA

    ... do x ...

#else

    ... do y ...

#endif

You'll need to adjust your build scripts to build the specific customer binaries. eg:

您需要调整构建脚本以构建特定的客户二进制文件。例如:

msbuild mysolution.sln /property:DefineConstants="CustomerA"

msbuild mysolution.sln /property:DefineConstants="CustomerB"