这是Jerry 2021年的第 24 篇文章,也是汪子熙公众号总共第 295 篇原创文章。
最近看到 SAP天天事 公众号发布的一篇文章:
基于 SAP Commerce Cloud,老娘舅构建的餐饮行业业务中台成功启用
老娘舅餐饮股份有限公司创建于1998年,目前位列中国餐饮百强第45位,是中国快餐十大品牌之一。
他们总结行业特色,对比解决方案,经过两年左右的产品选型和行业竞品参考,在对 SAP Commerce 产品管理的灵活性、渠道支持的多样性、订单处理的及时性和系统运行稳定性等多方面进行充分评估后,老娘舅最终选择了电商领域的领先产品套件 SAP Commerce Cloud 作为全渠道业务中台。
本文就从 SAP Commerce Cloud ( 电商云 ) 前台路由路径的自定义配置与开发的一个实际例子,来加深大家对其灵活性的认识。
Jerry 文章里介绍的 SAP Commerce Cloud UI,如无特殊说明,默认均为基于 SAP Spartacus 的新一代单页面 UI,而非传统的 JSP UI.
在 SAP Commerce Cloud 标准的 UI 里,我们在产品列表页面选择某个产品,点击进入明细页面:
从产品列表页面跳转到产品明细页面,是通过 Angular 标准的 Router 组件实现的。
下图产品明细页面的 url:
http://localhost:4202/electro...
很明显,这个产品明细页面的 url,由下列三个片段拼接而成:
静态的字符串 product
动态参数 358639, 即产品 ID
动态参数 DSC-N1,即产品名称
产品 ID 和产品名称维护在 SAP Commerce Cloud Backoffice 里:
SAP Commerce Cloud UI 的页面默认路由即 url 格式,通过 Spartacus 接口 RoutesConfig 来定义:
上图第35行的 paths 数组里包含的内容,即产品明细页面 ( 我们开发团队习惯简称为 PDP - Product Detail Page ) 的路由 url 模板。其中 :productCode 是一个占位符,运行时会被 36 行指定的参数映射数组( paramsMapping )里指定的 code,即 SAP Commerce Cloud 后台 Product 模型里的 code 字段的值取代。
之所以引入参数映射的机制,而不直接将 Product 模型字段 code 加入到 url 模板里,是为了让 url 模板语义上更加清晰,达到自描述的效果。因为 code 这个单词比较通用,到底指 Product code,还是指 Product category code 呢?而在 url 模板里使用表述更加精确的 productCode,再将其映射到 Product 模型的 code 字段,这样避免了可能产生的歧义。
当我们另外打开一个名称比较长的产品时,发现此时 url 的显示效果有些不尽如人意——产品名称中的空格,被 encode 处理成了 %20:
http://localhost:4202/electro...
因此本文借这个例子,介绍如何通过简单的二次开发,来更改 SAP Commerce Cloud ( 电商云 ) 里产品明细页面 url 的显示格式。
假设我们需要实现如下需求:
- 将产品名称中包含的空格,替换成连接号 "-" , 并显示在 url 里。
- 在 url 里显示产品的制造商信息,即 Product 模型的 manufacturer 字段。
具体实现步骤如下:
- 新建一个 Angular module,使用 Spartacus 提供的工具方法,ConfigModule.withConfig, 定义一条新的路由配置,键为 product,其值会覆盖掉 Spartacus 发布的同名默认路由配置:
第31行的 url 模板,包含了静态字符串 manufacturer, 其后紧跟 Product 模型上的同名字段,表明该产品的制造商名称,这样就实现了需求2.
而对于需求1即产品名称的空格替换为"-", 最常规的思路是,在产品数据从 SAP Commerce Cloud 后台返回前台之后,找一个可以编写自定义逻辑的钩子函数 ( hook ) 进行实现。
Jerry 之前的文章 Jerry在2020 SAP全球技术大会的分享:SAP Spartacus技术介绍的文字版 有这样一张图:
从 Commerce 系统读取产品主数据,读取的字段列表以 url 参数的形式出现在 API endpoint里。这些字段列表可以在 Connector 的静态配置点里进行配置:
Connector 并不会直接同 Commerce 交互,而是把请求转发给 Adapter,具体通信由 Adapter 完成,Connector 只负责调度 Adapter.
Connector 将 Adapter 取回的数据交给 NgRx 的 store 结构统一管理,后者的复杂度被 Facade 层所隐藏,而 Spartacus UI 组件只会同 Facade 层交互,进行数据绑定和页面展示。这体现了关注点分离的设计原则。因为 Commerce UI 组件和 Commerce 后台组件的数据模型存在差异,因此需要 Converter ( 有时也称 Normalizer ),在数据从 Commerce 取回,准备呈现在 UI 之前,先要通过 Converter 转换成适合 UI 展示的结构。
因此我创建了一个 ProductNameNormalizer,继承自Converter,实现其 convert 方法,在里面将 Product 模型的 name 字段,用正则表达式将包含的所有空格字符,替换成 "-".
上图第9行,我没有将替换后的结果值,存储到 Product 模型的 name 字段里,而是引入了一个新的 nameForUrl 字段,以避免影响其他可能同样基于该 name 字段的二次开发。
这个新引入的仅仅用于展示在浏览器地址栏里的字段,在很多 SAP 产品里都有广泛的应用,称呼也各不相同。然而它们都有着同样的特征:
*没有后台持久化存储,纯粹用于UI显示
- 为了弥补后台模型和 UI 模型字段差异而引入
- 其值通常都是动态计算而成
典型的一个例子是,后台存储了员工的出生年月,而 UI 显示的字段是年龄,那么 UI 模型里引入一个年龄字段,其值为当前年月减去员工出生年月。
在 SAP CRM 里,这种运行时根据某种规则,动态填充其值,供 UI 显示的字段,叫做 Calculated Field:
在 SAP CRM AET (Application Extension Tool) 里,Key User 可以通过简单的规则编辑器,维护 Calculated Field 的计算逻辑。当然,这些规则保存后,会编译生成对应的 ABAP 代码。
而到了 SAP Cloud for Customer 里,除了同样支持 SAP CRM Calculated Field 的规则编辑功能之外,还提供了两种允许二次开发人员,通过编程来填充用于 UI 临时显示字段值的方式:
- Transient Field
在 SAP Cloud for Customer BO 定义代码里,使用注解 Transient,将一个字段标注成 Transient 字段:
然后其值通过编写 AfterLoading 这个 hook 来填充。
- Dedicated Field
在 SAP C4C UI Designer 里将字段类型设置为 Dedicated Field,然后给其分配一个 Transformation,该 Transformation 由二次开发人员用 ABSL ( ABAP Scripting Language ) 实现,负责计算 Dedicated Field 的值。
回到本文介绍的 SAP Commerce Cloud UI 二次开发的例子。我的 Calculated Field,nameForUrl 的值,直接通过 ProductNameNormalizer 实现的 convert 方法填充。
至此这个需求的开发步骤已经结束。最后,将存放了把空格替换成 "-" 的 nameForUrl 字段(下图绿色高亮),映射到产品明细页面 url 模板 (下图红色高亮) 即可。
最后的效果:
现在的 url:
http://localhost:4204/electro...
是不是比之前的 url,可读性要好一些?
http://localhost:4202/electro...
感谢阅读。
更多阅读
- 从产品展示页面谈谈Hybris的特有概念和设计结构
- 从产品展示页面谈谈Hybris系列之二: DTO, Converter和Populator
- Hybris Enterprise Commerce Platform 服务层的设计与实现
- 从一个实际的例子出发,谈谈SAP Commerce Cloud电商云的UI自定义开发
- SAP Commerce Cloud (电商云) UI 的懒加载功能
- SAP CRM Fiori 应用和 SAP Commerce Cloud (电商云) UI 如何通过调整CSS来改变UI显示风格
- SAP 产品一脉相承的 UI 增强思路,在 SAP Commerce Cloud (电商云) UI 增强实现中的体现
- Jerry在2020 SAP全球技术大会的分享:SAP Spartacus技术介绍的文字版
- 一小时内在本地搭建SAP Commerce Cloud(电商云)的前后台运行环境
更多Jerry的原创文章,尽在:"汪子熙":