SAP 产品的 Field Extensibility

SAP 开发人员的工作职责,除了实现软件的功能性需求外,还会花费相当的精力实现一些非功能性需求,来满足所谓的 SAP Product Standard(产品标准)。这些产品标准,包含在 SAP 项目实施中大显身手的 Extensibility,为客户业务流程的安全运行保驾护航的 Security,也有看起来不太起眼,但实际上体现了 SAP 作为一家伟大企业所具有的社会担当的 Accessibility,以及 Internationalization 等等。
今天咱们就来说说 SAP 产品的 Extensibility。尽管 SAP 产品对现实世界中的业务流程做了一定程度的抽象,但是部分客户在实际使用过程中仍然会发现自己企业有些特殊的业务流程无法用 SAP 产品的标准功能来支持。此时 SAP 产品的 Extensibility 就有了用武之地:所谓 Extensibility,即 SAP 产品的底层框架提供了足够的灵活性,确保客户和实施伙伴借助 SAP 提供的标准工具,能够对 SAP 产品进行增强(Enhancement),以此来满足自己特殊的业务需求。这种增强和直接修改 SAP 产品(Modification)的区别在于,前者是 SAP 推荐的方式,增强实现本身和被增强的 SAP 标准资源是不同的实体,分别存储于不同的包内。每次版本升级时,增强实现本身不会受到任何影响;而修改,则改动了 SAP 产品的标准代码或模型,每次版本升级这些修改都会全部丢失掉。而且在基于 Netweaver 的 Cloud 产品里,比如 S/4HANA Cloud 和 SAP Cloud for Customer,客户和实施伙伴没有任何途径直接去修改 Netweaver 后台的资源。
SAP 产品的 Extensibility 分为 Field Extensibility 和 Process Extensibility。Field Extensibility,即用户可直接在浏览器里,通过简单的步骤在 UI 上期望的区域创建新的字段,我们称其为扩展字段(Extension Field)。有时候我们又会在这个术语前加上 Simple 的前缀,这个前缀强调,客户在创建新字段时,既不需要具备任何技术知识,也不需要了解该产品底层的数据模型的设计细节,而是通过类似我们在 Windows 系统下安装软件的向导一样,通过向导提示的简单步骤即可完成。Process Extensibility,强调流程的增强,即通过 SAP 提供的增强工具,比如 Business Add-In(BAdI), Post-Exit 等等,对 SAP 产品的标准流程做一定程度的增强。没有接触过 BAdI 和 Post-Exit 的朋友们,如果做过 Java Spring 开发,可以把 BAdI 类比成 Spring 里各种各样的 hook,把 Post-Exit 类比成 Spring AOP(面向切面编程)。
本文会介绍 SAP CRM 和 S/4HANA 的 Field Extensibility。本文的后续,SAP Cloud for Customer(C4C)的 Field Extensibility,会由 Jerry 的同事,SAP 成都研究院 C4C 开发团队的 Boris 来介绍。
SAP CRM Field Extensibility
SAP CRM 的扩展字段的创建通过 Application Enhancement Tool(AET)这个工具完成。用户在创建扩展字段之前,先要进入配置模式(Configuration mode),然后可以进行扩展字段创建的 UI 会自动被高亮。

点击高亮区域后,即可看到创建字段的按钮。点 Create Field 进入扩展字段的创建向导:

这里需要维护待创建扩展字段的明细信息,比如字段标签,字段类型,长度,字段所在的 BO 模型(下图的 ORDERADM_H)等等。

上图我创建了一个标签为 city name 的扩展字段,保存并激活后,在 UI 上显示如下。

这一切步骤仅仅几分钟内就可完成,然而背后发生的事情远远没有这么简单。为了实现 Simple Field Extensibility,SAP 开发人员需要进行一系列的开发,我们称其为 Extensibility registration and enablement。这种开发需要 SAP Extensibility 框架开发人员和 SAP 应用开发人员双方共同参与。因为尽管从客户眼中看到的效果,仅仅是 UI 新出现了一个扩展字段,然而这只是冰山一角——后台的数据库表,BO 模型和每一个交互层相关的接口数据结构也应该自动被该扩展字段所增强。
下面是一些例子,我在 UI 创建的标签为 city name 的扩展字段,自动出现在后台数据库表中:

和 CRM Order 相关的函数的接口结构也自动包含了这个扩展字段:

One Order 的 BO 模型的 BTAdminH 节点也自动被这个扩展字段增强。

那么 Extensibility 框架怎么知道当扩展字段被创建时,这些属于某个应用程序的资源也需要被增强呢?原来,SAP Extensibility 框架开发人员和 SAP 应用开发人员达成了一个契约:Extensibility 框架开发人员定义了一个注册表,应用开发人员如果想让自己负责的 UI 支持 Simple Field Extensibility,需要把自己应用的各种需要被框架自动增强的模型信息和数据库表信息填写到这个注册表里,这样当用户使用 AET 工具进行增强时,Extensibility 框架就知道到底有哪些应用层相关的模型也需要跟着增强。
这个注册表的外观见下图:

除了注册之外,应用开发人员还有很多其他事情要做,因此 SAP 内部有个 Application Extensibility Guideline 的编程规范,我们做开发时就是照着这个文档来的。
如果实施伙伴自己开发了一个 UI,也想让其支持 Simple Field Extensibility,那么也需要按照这个 Guideline 来开发。
我以前做过一个例子供大家参考:
注册表的填写:
https://blogs.sap.com/2013/10/25/step-by-step-to-enable-your-genil-component-with-aet
按照 Application Extensibility Guideline 让您的应用支持 Simple Field Extensibility
https://blogs.sap.com/2013/10/25/step-by-step-to-enable-your-genil-component-with-aet-part-two/
有的 CRM 顾问朋友们会不时问我,"为什么我的 AET UI 看不到 Create Field 的按钮,或者变成灰色了?”其实原因不外乎下面三种:
1. 您的用户没有 AET 使用权限
2. 您的系统上 AET 没有正确 setup 起来
3. 您选择的 UI 不可被 AET 增强(即 UI 对应的 UI 没有注册成"可扩展”)
在给 SAP 提 incident 之前,可以按照我这篇博客的步骤,自行去调试找到原因:
用 AET 创建的这些扩展字段,在运行时是如何画到 UI 上的呢?
简单地说,CRM WebClient UI 的视图配置信息(即视图上需要显示的字段明细,字段间的相对位置,字段标签等等),都维护在一个所谓 Configuration Context 的实体中,以 16 位的 GUID 标识。

Context 内容以 XML 格式存储在数据库表 BSP_DL_XMLSTRX2 里。

运行时,UI 框架首先从上述数据库表里把 XML 数据读出来,解析成 DOM, 然后根据 DOM 里每个节点对应的不同 UI 控件类型,实例化不同的 UI 控件。比如 XML 里定义的某个字段类型为 inputfield, 则创建一个 CL_THTMLB_INPUTFIELD 类的实例。Jerry 在之前的公众号文章 SAP UI 和 Salesforce UI 开发漫谈介绍过,每个 WebClient UI 支持的控件都会有一个类似 SAP UI5 中的 Render 类,负责生成该控件对应的原生 HTML 代码。而将 AET 创建出来的扩展字段添加到 UI 上,从 UI 视图配置的角度讲,仅仅是在 XML 源代码里增加了一个扩展字段对应的节点,如下图所示:

更多关于扩展字段在运行时的渲染原理讲解,请参考我的博客:
https://blogs.sap.com/2016/12/22/how-extension-field-created-by-aet-is-rendered-in-web-client-ui/
SAP S/4HANA Extensibility

和 SAP CRM 在具体的应用程序 UI 上直接创建扩展字段稍有不同,S/4HANA 通过一个统一的 Tile(Custom Fields and Logic)作为入口来创建扩展字段:

扩展字段的明细维护界面和 SAP CRM 的 AET 工具大同小异。下图的 Business Context 指用户希望这个字段出现在 S/4HANA Fiori 应用,Product Master 的明细界面的 General 区域内。

扩展字段创建完毕后,客户进到 Product Master 明细页面内,点击右键然后从 Available Fields 列表里选择出刚才创建的扩展字段,即可将此扩展字段显示在 Fiori UI 上。


S/4HANA 的应用开发人员需要做的事情和前一章节介绍的 SAP CRM 类似,同样需要做注册。
下图是 S/4HANA Extensibility 的注册表。高亮的一行,代表我在扩展字段创建对话框从 Business Context 下拉菜单里选中的"Product Master General":

上述注册表针对 Product Master General 维护了两个 ABAP DDIC include 结构,意思是一旦这个扩展字段创建保存后,会自动出现在这两个 DDIC include 结构上。
从注册表上方高亮的标签页还可看出,在 S/4HANA 里通过浏览器创建的这些扩展字段,除了直接显示在 Fiori UI 之外,还能放到 CDS view,OData 模型,Web Service,IDoc 这些模型中去。注册表里出现的这些选项仅仅表明它们可以支持用 Extensibility 扩展框架添加扩展字段,至于是否真正把扩展字段放进去,由客户自行决定。通过点击 Enable Usage 即可将扩展字段添加到对应模型中去。

那么显示在 Fiori UI 上的 S/4HANA 扩展字段,在运行时又是如何被渲染出来的呢?为了回答这个问题,我们先分析当我们把扩展字段添加到 Fiori UI 时,Fiori UI 发送给 S/4HANA 后台的 HTTP 请求到底包含了哪些信息。
使用 Jerry 之前的公众号文章 Jerry 和您聊聊 Chrome 开发者工具介绍的老套路,用 Chrome 开发者工具找到这个 HTTP 请求的明细。请求的 payload 是一个 JSON 字符串,保存到本地详细研究,里面有 7 个重要的字段。

1. jstype: sap.ui.comp.smartfield.SmartField
表明该扩展字段在 Fiori UI 视图中的实现类型为 Smart Field。什么是 Smart Field?它也是 UI5 提供的控件之一,但和 sap.m.Button, sap.m.Input 这些拥有具体类型的 UI 控件不同,Smart Field 在 XML 视图开发阶段,并没有和任何确定的 UI 显示类型绑定,实际上只是一个占位符。下图是一个 Smart Field 的例子,仅仅凭借这个 XML 视图片段,我们根本不知道 id 为 idPrice 的 Smart Field,在运行时到底会被渲染成一个什么样的 UI5 控件。相反,该控件的类型,在运行时才能决定下来,取决于其绑定的字段 Price 在 OData 模型的元数据中具有何种注解(annotation)。

在我的例子里,字段 Price 在元数据中被注解为一个拥有单位的 Decimal 字段,其代为字段为 OData 模型里另一个字段:CurrencyCode。

因此在运行时,这个 Smart Field 会被 UI5 框架渲染成两个 UI5 控件,一个控件显示价格的数字, 绑定到 OData 模型上的字段 Price,另一个控件显示价格单位,绑定到 OData 模型的字段 CurrencyCode。

更多 Smart Field 和渲染逻辑的讲解,请参考我的博客:
https://blogs.sap.com/2016/03/14/currency-example-how-smart-field-works/
2. id:mdm.cmd.product.maintain::sap.suite.ui.generic.template.ObjectPage.view.Detail::C_Product...
表明了该扩展字段到底添加到哪个 Fiori 应用的哪一个具体 UI 区域。ID 前半部分的 mdm.cmd.product.maintain 代表 S/4HANA Product Master 这个 Fiori 应用,sap.suite.ui.generic.template.ObjectPage.view.Detail 代表这个 Fiori 应用是基于 SAP Smart Template 框架构建而成,扩展字段所在的 UI 基于 Smart Template 的 ObjectPage 的 Detail 页面构建。
什么是 Smart Template 的 ObjectPage?请参考我的博客:
3. YY1_JDKminimumversionJ_PRD
Fiori UI 扩展字段绑定的 OData 模型的字段名称。我们可以做个实验:在 Fiori UI 上该扩展字段里随便维护一个值,比如"1.7", 然后保存。关掉 UI 再重新打开,很容易在 Chrome 开发者工具里观察到从后台返回的 OData 响应结构里,有一个名为"YY1_JDKminimumversionJ_PRD"的字段包含了"1.7"这个值。Fiori UI 的扩展字段正是绑定到了该模型字段上,因而能显示出"1.7"。

4. fileName
5. layer:CUSTOMER,packageName:$tmp
这几个字段需要联合起来解释。前面 CRM 章节已经介绍过,SAP CRM WebClient UI 视图的配置信息,以 XML 的格式维护在后台数据库表中。然而 S/4HANA Fiori 应用因为基于 UI5 开发,不存在这种配置信息对应的存储数据库表,而是用文件的方式,把扩展字段和 Fiori UI 的对应关系存储起来,放到一个特殊的仓库里。文件的内容大体上就是我现在正在介绍的从 Chrome 开发者工具里观察到的 JSON 字符串,文件存储的区域称为 LREP(Layered Repository)。LREP 实际是 ABAP 实现的一个文件系统,可以用 report /UIF/GET_CHANGES_4_TARGET 浏览其内容。
执行 report,最醒目的就是这几个 layer,这也是 LREP 命名的由来,一个分层的文件系统。

Vendor layer:即 SAP layer,包含 SAP 发布的标准内容。
Partner layer:Partner 可以基于 SAP layer 的内容做增强。例如同 CRM AET 一样,Partner 的增强可以通过配置放到一个可以传输的 ABAP 包里,那么 Partner 在 Fiori UI 上创建的扩展字段均存储在这个 ABAP 包内,从开发系统传到测试和生产系统。
Customer layer:客户通过 S/4HANA 的扩展工具做的增强,一般都配置为存储于 $tmp 包内,不可传输,对同一系统的其他所有用户均可见。
Draft layer:和本文主题无关,用于 S/4HANA 的 Draft 概念处理,参考 SAP help:https://help.sap.com/viewer/468a97775123488ab3345a0c48cadd8f/1709%20002/en-US/ed9aa41c563a44b18701529c8327db4d.html
User layer:存储 personalization 信息,仅对创建该资源的用户可见。
为什么要引入这个分层机制呢?还是为了实现文章开头提到的中心思想:确保合作伙伴和客户做的增强不会因为 SAP 的产品升级而丢失。通过内容的分层存储,SAP,合作伙伴和客户做的内容彼此隔离,互不影响。在运行时,假设对于同一 UI 模型,SAP,合作伙伴和客户均有各自的资源,则最终用户看到的 UI 是这些资源的一个并集,我们称产生这个并集的过程为 Merge。在 Merge 过程中如果遇到冲突,比如一个 UI 字段的标签,SAP,合作伙伴和客户均有各自的定义,则 Merge 结果以优先级最高的 layer 包含的内容为准。不同 layer 优先级从低到高,即上图 report 从上到下的 layer 依次为:
SAP->Partner->Customer->User。
再回到我们正在进行的 payload 分析。执行 report,结果如下。点击按钮显示 LREP 里这个文件的完整内容:

可以发现该文件内容就是我们在 Chrome 开发者工具里观察到的从 Fiori UI 发送到 S/4HANA 后台服务器的 HTTP 请求的 payload:

因此,我们在 Fiori UI 从右键菜单的 Available Fields 里选择扩展字段放到 Fiori UI 上时,Fiori UI 通过 HTTP 请求将该扩展字段的明细,即包含了迄今为止我们分析的这几个字段的 JSON 字符串发送到 S/4HANA 后台,存储在 LREP 中。
Fiori UI 与 S/4HANA LREP 的交互通过 sap.ui.fl.LrepConnector.js 完成,由后者调用 LREP 暴露出来的 service 来实现文件内容存储。

6. reference: mdm.cmd.product.maintain.Component
Product Master 这个 Fiori 应用的 Component ID,可以在 BSP 应用 MD_PROD_MAS_S1 的 Component.js 里找到。前面说过了,Product Master 这个 Fiori 应用基于 Smart Template 构建,并没有自己的前端实现,因此 Component.js 只是一个 wrapper,仅有不到 6 行代码。

当包含了扩展字段的 Fiori UI 即将渲染时,首先有一个 HTTP 请求将待渲染 UI 包含的所有扩展字段信息从 LREP 中读取出来。注意下图蓝色高亮区域内的/sap/bc/lrep/flex/data, 这就是 S/4HANA 后台 LREP 暴露给 Fiori UI 的存储服务。

Fiori UI 读取到 LREP 返回的 JSON 后,解析到 changeType 为 addFields,于是调用 Fiori UI 框架对应的处理逻辑,根据 JSON 里包含的扩展字段明细将其渲染出来。这实际上就是前面提到的,SAP layer 的 Fiori 标准 UI 同 Customer layer 的扩展字段的 Merge 动作。
扩展字段 Merge 到 Fiori UI 的入口在 AddFields.applyChange:

addElementIntoGroupElement 会将扩展字段添加到 Fiori UI 对应的区域内:

addElementIntoGroupElement 又会调用 createControl 将扩展字段的定义转换成对应的 UI5 控件实例,后者的 Render 负责将控件实例渲染成原生的 HTML 代码。至此,S/4HANA 扩展字段的渲染就完成了。


要获取更多 Jerry 的原创技术文章,请关注公众号"汪子熙"。
版权声明: 本文为 InfoQ 作者【Jerry Wang】的原创文章。
原文链接:【http://xie.infoq.cn/article/36de008c0d393b08e4ae6c9bb】。文章转载请联系作者。
评论