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的原创技术文章,请关注公众号"汪子熙"或者扫描下面二维码: