一种经典的客户关系管理系统 (CRM) 订单模型的设计与实现

笔者在 SAP 领域工作多年,对 SAP 应用的理解就是:模型以及基于模型的增删改查。
只是同我们大学专业课学习时完成的家庭作业相比,SAP 模型的复杂程度增加了好几个数量级。
和传统的增删改查相比,以订单编排领域为例,SAP 订单模型的"增",还需要考虑实际业务流程中各种类型的前置和后序订单,即 SAP 使用的术语文档流(Document Flow)。

而"改", 除了订单自身状态的迁移外,还包括订单模型提供的各种可执行逻辑。这些逻辑既包括订单模型本身字段的更改,也可以包括订单与第三方系统的交互。在很多上下文里,我们称这些逻辑为 Action。
如下图右下角所示:

既然订单模型复杂度如此之高,那么引入一种精良的能支持企业级订单编排应用的高质量建模方式,就显得至关重要。
随便看些例子,SAP CRM 总共支持多少种标准的订单类型?下图中 BUS2000 开头的就是不同的订单类型,我没有具体数过,但是几十种总是有的。

而 SAP Cloud for Customer,虽然位于 CRM 命名空间下面的 Business Object 的数量比 SAP CRM 要少一些,但是基本的用于实现销售自动化流程的订单模型仍然一应俱全。

我们先来看 SAP CRM 的订单模型。有没有可能用一套模型来描述 SAP CRM 支持的几十种订单类型呢?有,那就是 SAP CRM One Order 模型,其自描述的名称就体现了该模型的特色。
下面是 SAP CRM 应用的架构图:

其中 One Order 框架从架构上讲,位于上图红色区域内,包括数据库表,ABAP 结构体以及操作它们的 API 代码。
笔者刚刚接触 One Order 模型的时候,吃惊地发现,竟然没有一个比较直观的图形化界面,能够显示出这个模型的全貌。不过瑕不掩瑜,对于一个诞生于 20 年前的框架来说,我们不应该用 20 年后的标准来苛求它。
我们想象一下,不同类型的订单,有什么共同点?无非每种订单都有抬头结构,行项目。有的结构,从业务上说可以同时出现在订单的抬头和行项目,比如参与订单的业务伙伴明细(Involved parties), 组织架构(Organization Unit)等等。有的字段只有行项目才能出现,比如卖出的产品信息(Product, Scheduled Line)。
SAP One Order 建模的原理,类似我们小时候玩的积木。

组成 One Order 模型最小粒度的单元,就是一个个扮演积木作用的结构体,在事务码 CRMC_OBJECTS 里查看。下图是这些结构体的列表,如果 SAP 标准的结构体不能满足需要,客户仍然可以自行创建新的结构体。

然后我们用搭积木的方式,将业务上具有关联关系的若干结构体组合起来,共同分配给某个订单类型,比如描述服务流程的订单类型 BUS2000116,就由下列这些结构体组成:

有了模型之后,剩下的就是实现基于这些模型的增删改查操作,即 ABAP 编程。
One Order API 的代码实现原理,实际上就是设计模式里的模板(Template)模式和观察-发布者模式的结合体。我们学习模板模式的时候,有一个经典的例子,上帝通过模板模式主宰芸芸众生的生老病死。

我们每个人被父母实例化出来之后,只能被动地实现上帝在模板里定义好的四个方法:生,老,病,死,而不能够更改这个模板本身,比如调换这四个方法的顺序。即使是乔布斯,也没有办法给自己添加一个"永生"的方法。听起来很残酷,但这是事实。
那么,One Order 框架里,作为 One Order 应用的上帝,定义了哪些模板方法?
事务码 CRMV_EVENT,指定 BUS2000116, 执行:

得到下图列表。红色的第一列,就是前文提到的组成 One Order 模型的积木。蓝色的第二列,是这些积木对发生在自己身上的感兴趣的事件列表。从图中可以看到这些事件名称都是自描述的,比如 AFTER_CREATE, BEFORE_CHANGE, BEFORE_DELETE 等等。
第三列黑色的 ABAP 函数,就是这些事件的监听函数。
这些监听函数的后缀 EC 代表 Event Callback.

借助上述框架,One Order 应用的开发人员的开发工作就变得无比轻松:
(1) 通过搭积木的方式,定义出自己应用需要的 One Order 模型(2) 实现模型里需要关注的事件对应的监听函数。
至于这些监听函数什么时候被调用到?应用开发人员完全不用操心。
由此我们能发现,One Order 框架的实现,把编程复杂度从应用开发人员身上转移到了框架实现身上。
One Order 框架内部的实现比较复杂。通常情况下,One Order 框架的使用者只需要了解 CRM_ORDER_READ, CRM_ORDER_MAINTAIN 等 API 的用法即可。
One Order 的 API 之一,为消费者提供修改操作的 CRM_ORDER_MAINTAIN, 所有 SAP 标准支持的结构体都作为输入参数之一出现在参数列表里:

这种设计方法虽然让参数列表显得有点冗长,但是从另一方面看,也起到了自描述的效果, 确保 API 的使用者即使不阅读文档,仅凭浏览这些参数本身,就能大概了解该 API 到底支持 One Order 哪些数据的修改。
这也符合那份著名的来自 Google 的 API 设计最佳实践文档里提到的,好的 API 应该满足的条件之一:易学易用,自描述,不易造成误解。

SAP CRM 的部分功能迁移到 SAP S/4HANA 后,部分实现做了一些改造,其中就包括 One Order 的改造。

为什么要改造?因为 SAP CRM 搬到了 S/4HANA 上,而 S/4HANA 的一个强大之处,即 S/4HANA 在 SAP 历史上第一次实现了 OLTP 和 OLAP 的完美结合,即一套系统的唯一数据源,可以同时满足 Transaction 事务型应用和 Analytics 分析报表型应用的需要。

而 SAP CRM One Order 没有改造之前的模型是无法和 S/4HANA 的上述特性匹配的。
改造之前,每个组成 One Order 模型最小粒度的结构体,都有自己独立的一张专属数据库表,命名规范一般是 CRMD_加上结构体名。
这套底层存储模型如果原封不动地搬到 S/4HANA 里,在运行报表统计等应用时会出现性能问题——为了取出报表结果,后台需要在很多个结构体的存储表中做各种数据库表的内外连接操作。当参与连接操作的数据库表尺寸增长到一定数量级后,整个应用的性能表现不佳。笔者也参与了性能评测,最后我们决定对 One Order 的底层数据模型做改造。
因为留给我们从调研到改造的原型开发,再到正式开发一共只有八个月的时间,因此我们选择了一种代价最小,对 One Order 框架改动最小的方式。
首先我们抛弃了之前每个结构体拥有一张专属数据库表的做法,在 S/4HANA 里,每种订单类型只拥有两张表,一张存储抬头级别的数据,另一张存放行项目数据。之前散落在不同结构体表中的字段,如今统一维护在这两张表里。由于所有的字段都平铺在这两张表里,我们内部形象地称其为平坦表(Flattened Table)。
存储模型大大简化之后,我们基于这两张表再创建 CDS view,让上层的报表应用消费。这样改造后简化的模型,能满足 S/4HANA 中 OLAP 应用的需求。
针对 S/4HANA OLTP 应用的改造,用一句话概括,就是我们采用设计模式里的适配器模式(Adapter), 在 API 与简化后的数据库表之间引入一个微型的中间件,扮演 Adapter 的角色。
当消费者通过 One Order API 进行读操作时,中间件负责把存储在简化后的数据表中的数据进行还原,再填充到 One Order API 上层的缓存中。对后者来说,它对底层存储模型发生的变化毫不知情,因为 Adapter 封装了底层数据读取的逻辑并做了格式转换,所以 One Order API 上层不需要做任何改动,也完全能够像在 SAP CRM 里一样正常运行。

而当消费者调用 One Order API 进行写操作时,在存储于各个结构体对应的缓存中的数据持久化到数据库之前,同样是 Adapter 负责把这些分散在不同缓存结构中的数据做一个合并,合并后的结构体再写入平坦表。

讲完了 CRM One Order 订单模型的设计,我们再来简单看看 SAP Cloud for Customer 的订单模型设计。
虽然 SAP Cloud for Customer 的后台对客户和 Partners 不可见,但我们仍然可以从合法渠道获得一些其订单模型的设计信息。
从 SAP 社区上这位 SAP 员工的回复,我们得知 ESF2 和 BOPF 有很多相似之处,设计理念类似,但 ESF2 主要用于部署在云端的产品,比如 SAP Cloud for Customer 上 Business Object 的开发,而后者主要服务于 On Premises 解决方案比如 S/4HANA。
同之前介绍的 SAP CRM One Order 框架一样,通过 BOPF 实现的订单模型,同样由若干个结构体通过搭积木的方式组成,这些结构体如上图红色高亮区域所示,每个结构体也有自己的专属存储数据库表。而 SAP CRM One Order 里每个结构体的事件监听函数,采取的是 ABAP 传统的面向过程的函数实现,而 BOPF 则采取了实现指定接口的 ABAP 类,二者原理相同,只是实现细节有差异。
SAP C4C 的订单模型,虽然和 SAP CRM 传统的 One Order 模型一样,每个结构体拥有一张专属的数据库表,但是在运行报表程序时并不会出现性能问题,这是怎么做到的?
答案是采用了 TREX,一个专为只读报表应用优化过的存储仓库。换句话说,SAP C4C 的事务处理和报表处理使用的是两套不同的存储系统,这一点和 S/4HANA 不同。

SAP Cloud for Customer 的订单模型,在 Cloud Application Studio 里对客户和 Partners 是可见的,大家感兴趣的可以自行去查看。

总结
本文首先详细介绍了 SAP CRM 系统里订单模型的演进历史和设计原理,以及消费该订单模型的 API,接着介绍了这种模型迁移到 S/4HANA 系统时面临的挑战,以及笔者所在项目团队给出的迁移重构方案。最后将另一款云端 CRM 系统的订单模型进行类比,希望对工作于客户关系管理领域的读者能有所帮助。
版权声明: 本文为 InfoQ 作者【Jerry Wang】的原创文章。
原文链接:【http://xie.infoq.cn/article/e8435fba094bcbfc1f041d1b0】。文章转载请联系作者。
评论