因此,我们向客户出售了B2B软件产品。
在演讲中,他喜欢一切,但在实施过程中发现仍然有些不合适。 当然,您可以说您需要遵循“最佳实践”,并将自己转变为产品,而不是相反。 如果您有很强的品牌(例如,三个大字母,并且可以发送所有三个小字母),这可能会起作用。 否则,他们会迅速向您解释,客户由于其独特的业务流程而实现了所有目标,因此,我们最好更改您的产品,否则将无法正常工作。 可以选择拒绝并提及已经购买了许可证的事实,并且潜水艇无处可去。 但是在相对狭窄的市场中,这种策略在很长一段时间内都行不通。
我们必须修改。
方法
有几种基本的产品适应方法。
整体式
任何更改都直接对产品的源代码进行,但包含在某些选项中。 通常,在此类产品中,存在带有设置的怪异形式,为了不引起混淆,已为其分配了编号或代码。 这种方法的缺点是源代码会变成很大的意大利面,其中有很多不同的用例,以至于维护起来非常长且昂贵。 每个后续选项都需要越来越多的资源。 这种产品的性能也有很多不足之处。 而且,如果产品的编写语言不支持继承和多态性等现代实践,那么一切都会变得很可悲。
复制
将为客户提供产品的完整源代码,并具有对其进行修改的许可证。 通常,此类供应商会告诉客户他们不会自己修改产品,因为这种产品价格太昂贵(与销售许可证相比,出售许可证的利润要高得多)。 但是他们有熟悉的外包商,他们在第三国的某个地方雇用了相对廉价且高质量的员工,他们随时准备为他们提供帮助。 在某些情况下,客户的专家会直接进行改进(如果他们有人员配备的话)。 在这种情况下,将源代码作为起点,并且修改后的代码将与原始内容没有任何关系,并且将保持自己的生命。 在这种情况下,您可以安全地删除至少一半的原始产品,并用自己的逻辑替换它。
合并
这是前两种方法的混合。 但是在其中,更正代码的开发人员应始终记住:“合并即将到来”。 当发行新版本的源产品时,在大多数情况下,它将必须手动合并源代码和修改后的代码中的更改。 问题在于,在任何冲突中都必须记住为什么要进行某些更改,而这可能是很久以前的事情了。 而且,如果在原始产品中执行了代码重构(例如,代码块被简单地重新排列了),那么合并将非常耗时。
模块化
逻辑上是最正确的方法。 在这种情况下,产品的源代码不会更改,并且会添加其他模块来扩展功能。 但是,为了实现这种方案,产品必须具有允许以这种方式扩展的体系结构。
内容描述
进一步通过示例,我将展示我们如何扩展基于开放和免费的
lsFusion平台开发的产品。
系统的关键要素是模块。 模块是带有
lsf扩展名的文本文件,其中包含
lsFusion代码 。 在每个模块中,都声明了域逻辑(函数,类,动作)和表示逻辑(表单,导航器)。 通常,模块位于目录中,由逻辑原理划分。 产品是实现其功能的模块的集合,并存储在单独的存储库中。
模块是相互依赖的。 如果一个模块使用其逻辑(例如,引用属性或形式),则依赖于另一个模块。
当出现新客户端时,将为其启动单独的存储库(Git或Subversion),其中将创建具有必要修改的模块。 该存储库定义了所谓的顶层模块。 服务器启动时,将仅连接那些模块,它直接或通过其他模块传递依赖于这些模块。 这样,客户不仅可以使用产品的所有功能,还可以使用他需要的部分。
Jenkins创建一个任务,将产品和客户端模块组合到一个jar文件中,然后将其安装在生产或测试服务器上。
考虑实践中出现的几种主要改进案例:
假设我们在产品中有一个
订购模块,它描述了标准订购逻辑:
客户
X希望为订单行添加折扣百分比和折扣价格。
首先,在客户存储库中创建一个新的
OrderX模块。 在其标头中,一个依赖项放置在原始
Order模块上:
在此模块中,我们声明新的属性,将在这些属性下在表中创建其他字段,并将其添加到表单中:
我们无法提供折扣价。 当初始价格或折扣百分比发生变化时,它将作为单独的事件进行计算:
现在,您需要在订单行上更改金额的计算(它必须考虑到我们新创建的折扣价)。 为此,我们通常创建某些其他模块可以插入其行为的“入口点”。 代替在Order模块中对sum属性的初始声明,我们使用以下代码:
在这种情况下,
sum属性的值将在一个CASE中收集,其中WHEN可以分散在不同的模块中。 可以保证,如果模块A依赖于模块B,则模块B的所有WHEN都将晚于模块A的WHEN。为了正确计算折现
金额 ,将以下声明添加到
OrderX模块:
结果,如果设置了折扣,则该金额将受其限制,否则受原始表达的影响。
假设客户想要添加一个限制,即订单金额不得超过特定的指定金额。 在同一
OrderX模块中,
我们声明一个将存储约束值的属性,并将其添加到标准
选项表单中(如果需要,可以使用设置创建一个单独的表单):
然后,在同一个模块中,我们声明订单金额,在表单上显示订单金额,并为其超出部分添加限制:
最后,客户要求稍微更改订单编辑表单的设计:在订单标题的行左侧使用分隔符,并始终以两个字符的精度显示价格。 为此,将以下代码添加到其模块,该模块更改了订单表单的标准生成设计:
结果,我们获得了两个
Order模块(在产品中),在其中实现了订单的基本逻辑;在
OrderX中 (在客户处),实现了必要的折扣逻辑:
应该注意的是,
OrderX模块可以称为
OrderDiscount,并直接转移到产品中。 然后,如有必要,每个客户都可以轻松地通过折扣连接功能。
这远非平台为扩展单个模块中的功能提供的所有可能性。 例如,使用继承,您可以模块化实现
寄存器逻辑。
如果产品的源代码中有任何更改与从属模块中的代码相矛盾,则在服务器启动时将生成错误。 例如,如果在
Order模块中
删除了
订单 ,则在启动时会出现一个错误,即在
OrderX模块中找不到
订单 。 同样,该错误将在
IDE中突出显示。 此外,IDE还具有搜索项目中所有错误的功能,该功能使您可以识别由于更新产品版本而发生的所有问题。
实际上,我们将(产品和所有客户的)所有存储库都连接到同一个项目,因此我们可以冷静地重构产品,同时更改使用该产品的客户模块中的逻辑。
结论
这种微模块架构具有以下优点:
- 每个客户仅连接他所需的功能 。 他的数据库的结构仅包含他使用的那些字段。 最终解决方案的接口不包含不必要的元素。 服务器和客户端不执行不必要的事件和检查。
- 基本功能更改的灵活性 。 您可以直接在客户的项目中对绝对任何形式的产品进行更改,添加事件,新对象和属性,操作,更改设计等等。
- 大大加快了客户要求的新改进的交付 。 每次更改请求时,您都无需考虑它将如何影响其他客户。 因此,可以进行许多改进,并尽快(通常在几个小时内)投入运行。
- 一种更方便的方案,用于扩展产品的功能 。 首先,可以为准备试用的特定客户提供任何功能,然后,如果成功实施,则将模块完全转移到产品存储库中。
- 代码库独立性 。 由于根据客户服务合同提供了许多改进,因此正式而言,根据这些合同开发的整个代码属于客户。 通过这种方案,可以确保将属于卖方的产品代码与客户拥有的代码完全分开。 根据要求,我们将存储库转移到客户的服务器,客户可以在该服务器上与自己的开发人员一起修改所需的功能。 此外,如果供应商许可了单个产品模块,则客户将没有没有许可证的模块的源代码。 因此,他没有违反许可条件的独立连接它们的技术能力。
上面在编程扩展的帮助下描述的模块化方案通常被称为
mix in 。 例如,Microsoft Dynamics最近引入了扩展的概念,该概念还允许您扩展基本模块。 但是,在那里需要更低级别的编程,从而需要更高的开发人员资格。 另外,与lsFusion不同,事件和限制的扩展要求产品具有初始的“入口点”才能利用此优势。
目前,根据上述方案,我们支持并实施了一个零售
系统ERP系统 ,该
系统具有30多个相对较大的客户,其中包括1000多个模块。 在客户中有快速消费品网络,以及药店,服装店,百货连锁店,批发商等。 在产品中,根据所使用的行业和业务流程,分别连接了不同类别的模块。