ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ABAP Cloud转型:传统Report程序迁移Fiori与Application Job实战

ABAP Cloud转型:传统Report程序迁移Fiori与Application Job实战 这两年做 S/4HANA 升级和系统合规改造的朋友应该都有一个共同的感受老的带SELECT-OPTIONS、START-OF-SELECTION的 Report 程序在 ABAP Cloud 开发模型里越来越不受待见。代码审计工具一跑几十个满屏WRITE的经典报表全被标记成“不云就绪”。问题不是这些程序跑不动而是你在新的扩展开发里再想新建一个“类 Report”的东西会被严格检查直接拦下来。这种时候你不得不面对一个现实Fiori 和 Application Job 已经不只是前端展示层面的变化而是老 Report 程序在新体系里的两条正儿八经的继任路径。这篇文章我按照最近一个从 ECC 迁移到 S/4HANA、同时推行 Clean Core 项目里的实际处理思路来写。不讲虚的不怕说大白话直接聊聊什么样的 Report 该去 Fiori什么样的该转成 Application Job以及那些老代码里的交互逻辑和批量调度逻辑到底怎么一步步搬过去。1. ABAP Cloud 到底在报什么错老 Report 的三大不可用先说清楚 ABAP Cloud 不是“只能跑在云上”的噱头。它是一个开发规范也可以理解成一套“检查清单”。只要你在 S/4HANA 里开启严格模式或者在新项目的扩展开发里遵守 ABAP Cloud 模型就会发现自己习以为常的老三样全都不灵了。1.1 界面技术退出Dynpro 选择屏幕与 WRITE 输出传统 Report 最典型的样子就是一段定义选择屏幕的代码里面排着PARAMETERS、SELECT-OPTIONS然后用WRITE把数据一行一行打出来或者把结果塞给 ALV 控件。这套组合在 ABAP Cloud 模型里基本没戏。WRITE这种面向列的经典输出命令在新的编译环境里会直接报语法检查错误甚至在很多场景下连标准类都不允许你直接使用旧的屏幕描述符。我见过有同事试图在一个新产品里复用老报表的 ALV 输出逻辑结果发现查了半天的REUSE_ALV_GRID_DISPLAY在很多 ABAP Cloud 环境里根本不在允许清单中。就算 On-Premise 的 S/4HANA 还能跑代码审计工具也会把这类调用标记为“不符合 Clean Core”。所以界面层的结论很明确凡是需要人坐在电脑前筛选、查看、录入、确认的旧 Report未来只有一个主流出口就是 Fiori。至于中间那层数据怎么端过去是用 CDS 自定义模型还是 OData 服务后面单独讲。1.2 数据库访问收紧Open SQL 裸查表的时代结束ABAP Cloud 模型下应用代码不能像以前那样肆无忌惮地直接SELECT * FROM mara然后想怎么拼 SQL 就怎么拼。ABAP 语言也把对底层表的直接访问和字典对象的耦合定义为“脆弱点”。新模型希望你对外提供数据时先建立好 CDS 视图作为领域模型别人再通过这个 CDS 视图消费数据。这句话听着抽象实际影响却非常重。以前报表里的核心逻辑往往是这样的一个几十行的SELECT加上一堆WHERE条件再把结果拼到内表里中间可能还要嵌套子查询。ABAP Cloud 里这套东西的重心前移到 CDS 模型层你在 CDS 里定义关联、权限、计算字段应用层只是消费这个模型。刚开始我很不适应因为多了一层抽象的“建模”工作。但做了几个 Fiori 改造之后我意识到这其实是把报表逻辑从“一次性程序”变成了“可复用资产”。同一个数据模型前端 Fiori Elements 用后台 Application Job 也用两边消费的是同一个 CDS 视图数据口径天然统一不会出现前台报表和后台批处理结果对不上的情况。1.3 调度和变式变了SM36 不是默认答案除了界面和数据库访问第三个卡点是后台调度。传统 Report 做批处理就靠 SM36 建后台作业配置变量变式然后丢给系统定时跑。ABAP Cloud 模型里这种“作业变式”的老玩法被新的 Application Job 框架替代。不是告诉你 SM36 没了而是新开发的批处理程序不应该再依赖这套老框架。Application Job 提供了更干净的目录和模板机制作业参数不再依赖容易过期的变式而是通过结构化参数传入。这个点单独放到第三章细讲因为它是很多老开发最容易忽略的继任方向。2. 交互型程序继任从 Report 到 Fiori Elements现在说正题。老 Report 程序里有一类人机交互很强用户要不停换筛选条件、看汇总、点按钮下钻。这一类程序最适合的继任方案是 Fiori准确说是 Fiori Elements 里的 List Report 和 Object Page 组合。2.1 哪些 Report 该走 Fiori哪些不该走先泼个冷水不是所有 Report 都值得改成 Fiori。我判断的标准就三条。第一有没有真人坐在前面看。如果程序跑完以后列表没人看或者只是生成一个文件丢到服务器上那去 Fiori 是浪费。第二有没有交互操作。报表里如果只是展示没有点击跳转、没有状态更新、没有编辑框那 Fiori 的优势也发挥不出来。第三是不是高频使用。一年跑两次、看两眼就完事的固化报表改造投入很可能回不了本。反过来最值得优先迁到 Fiori 的是那种用户每天打开、反复筛选、还要做后续动作的程序。比如订单查询、审批状态跟踪、成本汇总加逐行下钻。这类程序迁到 Fiori 之后用户体验不是变好一点是直接换代。用户不用再记事务码不用在经典屏幕里靠 TAB 切字段在手机或网页端就能完成筛选和查看。2.2 落地路径CDS 自定义模型 RAP 行为 服务绑定新 Fiori 开发在 ABAP Cloud 里基本就是一条固定路线先用 CDS 建数据模型再用行为定义管理增删改查然后创建服务绑定最后用 Fiori Elements 生成界面。这里重点说自定义模型。你在 Eclipse 里新建一个 CDS 视图给它起了个类似ZC_FIORI_ORDER的名字这就算是一个自定义模型了。别小看这一步老 Report 里大量取数逻辑都要“翻译”成 CDS 注解和关联。简单示例长这样AccessControl.authorizationCheck: #CHECK EndUserText.label: 订单查询模型 define root view entity ZC_Fiori_Order as select from i_order as ord association [0..1] to ZI_Customer as cust on ord.custid cust.custid { key ord.uuid as OrderUuid, ord.ordernumber as OrderNumber, ord.status as Status, ord.createtime as CreateTime, cust.customername as CustomerName, UI.lineItem: [{ position: 10, label: 订单金额 }] ord.amount as Amount }注意几个细节。一是权限控制AccessControl.authorizationCheck: #CHECK建议加上别让 Fiori 前端变成裸奔查询。二是对外可读性字段用英文别名前端可以显示中文 label但底层语义要清晰。三是别不加选择地查底层表优先基于标准 CDS 视图来建自定义模型这样数据口径有保障。模型建完之后如果程序只需要查询行为定义可以不创建或者只创建一个只读行为。然后创建服务绑定选择 OData v2 或 v4把 CDS 视图暴露成服务。最后在前端工程或 Fiori Elements 里连接到这个服务配置好列表字段、默认排序、筛选条件一个 Fiori 报表就有了雏形。这里插一个我的实际体验老 Report 里常常有一段“根据用户权限过滤公司代码”的逻辑在新模型里不要直接写在 CDS 查询里。CDS 的 ODATA 服务会自动根据权限对象做行级过滤前提是权限检查和底层权限对象配置好。把权限逻辑从代码里挪到框架层一开始很别扭但后期加新用户、调权限省了不知道多少 Release 维护。2.3 老 ALV 的交互逻辑怎么搬到前端老 ALV 有几个常见交互列排序、列宽调整、合计栏、点击行下钻。这些东西搬到 Fiori Elements 并没有那么费劲。列排序和列宽前端自带后端不用管。合计栏在 CDS 注解里写Aggregation.default: #SUM效果和 ALV 的 SUM 差不多。点击行下钻Fiori Elements 的 List Report 天然支持配置目标导航点击记录跳到 Object Page或者跳另一个报表应用对应老程序里的 PAI 双击事件。唯一要费点心思的是老程序里的“自定义按钮”。以前很多 Report 会在 ALV 工具栏加一个“批量过账”或“导出格式A”的按钮按钮背后是一堆业务逻辑。到了 Fiori Elements这些按钮要定义成行为或自定义控制页。比如“批量过账”你应该在行为定义里创建一个动作方法前端按钮触发后调用后端这样逻辑还是留在 ABAP 里前端只是透传。写行为定义时有个小坑动作方法不要直接对数据库做大量循环读写。一次过账几千行你在方法里逐条MODIFY不仅性能差还会把数据库锁拖很久。正确做法是把待处理行的UUID集合作为参数传进去在方法里拼一次批量操作或者干脆丢给后台 Application Job 处理。这个取舍其实就引出了下一条路线。3. 批量型程序继任从 Report 到 Application Job和交互型 Report 相对的是那些没人盯着的批处理程序。以前它们大多也是 Report定义选择屏幕用变式预设好参数SM36 定时跑。换了 ABAP Cloud 之后这一类程序的继任者是 Application Job。3.1 Application Job 和传统后台作业的区别Application Job 不是一个“新事务码下的 SM36”而是把你的一段 ABAP 逻辑注册成一个可管理的“作业模板”然后由统一的框架负责调度、监控、日志。它的好处我从三个角度讲。对用户而言以前调度后台作业必须要懂事务码要找同事帮忙维护变式现在用户可以直接在 Fiori 界面里创建实例化作业按模板填写参数系统自动生成可执行的作业。对开发者而言作业类不再和某个报表绑定一个作业类可以被多个目录和模板复用参数结构标准化。对运维而言作业的运行结果、日志、消息都以标准形式集中展示不再像以前那样要自己在作业日志里翻 INPUT 和 OUTPUT 输出。我改造的一个典型例子是这样的老系统里有一个“月度成本重估”Report前端参数有期间、公司代码、是否测试模式跑完以后把结果打在一个 ALV 里实际上没有人看这个 ALV所有人只看后续的财务结果。这种程序以前放在 SM36 里每次到了月底得有人手动维护变式、检查作业状态。后来改成了 Application Job用户自己在 Fiori 应用里选“成本重估模板”填上期间和公司代码选择“立即运行”或者“周期运行”就行。3.2 一个可复用的 Job 类长什么样Application Job 里一个作业类的核心是实现标准接口IF_APJ_RT_EXECUTION。这个接口里有一个EXECUTE_JOB方法框架会把作业参数、运行上下文都传进来。写一个最简单的作业类长这样CLASS zcl_cost_revaluation_job DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES if_apj_rt_execution. ENDCLASS. CLASS zcl_cost_revaluation_job IMPLEMENTATION. METHOD if_apj_rt_execution~execute_job. 1. 读取作业参数 2. 执行批量逻辑 3. 把运行结果写到业务表/日志 ENDMETHOD. ENDCLASS.实际开发中参数读取一般依赖注入结构或 XCO 方式不同版本写法略不一样但最核心的逻辑不变你的老 Report 里从START-OF-SELECTION到END-OF-SELECTION之间的那段“干活逻辑”整个搬进EXECUTE_JOB方法即可。有一点要特别注意老 Report 的变式是一个“参数快照”没有结构性约束改错了很难排查。Application Job 的参数是通过模板定义的每个参数有没有、是什么类型、是否必填都清清楚楚。所以我建议你新建作业类时先设计好“作业参数结构”哪怕多花十分钟也比将来让用户漏填、填错要好。3.3 创建 Catalog 与模板把变式收进前端类写完之后真正让业务上手的步骤是注册目录和模板。先说目录。进入 Fiori 的 Application Job 管理应用创建一个 Job Catalog然后把刚才那个作业类挂到目录底下。一个目录可以挂多个作业类相当于以前的一类作业组。再建模板。模板的意义是固化“参数预填”。以前变式是管理员在 SM36 里填好参数保存出来现在的模板可以做成几套比如“月度正式运行模板”“月度测试运行模板”“季度特殊重估模板”。用户打开模板看到的不是空表单而是已经预填好的期间、公司代码、测试模式只需要确认或微调再提交。从实际运营角度说这样做的最大价值是减少误操作。以前用户新建一个后台作业变式选错、期间填成上个月都是常见事故。现在模板把可选参数限于系统提示的范围提交时框架还会做一致性校验基本把低级错误挡掉了。模板创建完成后系统会为每一个作业实例保留运行日志。定一个复查习惯每次正式批处理跑完别只看作业状态点开消息列表逐条看有没有 Warning特别是有没有出现“业务异常已被吞掉”的情况。这个问题我放到第五章重点讲。4. 选型决策三种程序的拆解与混排策略说了这么多到了一个实操项目里你会发现最难的往往不是技术上怎么改而是“这条老代码到底往哪条路走”。面对一个两百行、看起来又像展示又像批处理的 Report我习惯用一套三层递进的方法来拆。4.1 三连问先判断程序属性第一个问题有没有人坐在屏幕前等这个结果有去 Fiori没有继续问。第二个问题程序是周期性无人值守执行的批任务吗是去 Application Job不是继续问。第三个问题这个程序是不是可以被拆成一个“数据查询”加一个“后台动作”能拆查询部分做 Fiori动作部分做 Application Job不能拆再考虑用 Fiori Elements 加上行为方法统包。拿我改过的一个报表举例。场景是“销售订单批量状态穿梭”运营人员要先在界面里筛出符合条件的订单看一遍列表再点按钮把订单从“待确认”改成“已确认”。按传统写法这很可能是一个带屏幕输出的 ALV上面放一个按钮。我拆的时候就把“筛选列表”留在了 Fiori 应用里把“状态穿梭”做成了一个行为方法后台异步调用一个作业类去执行批量更新前端只负责提示“已提交处理”。这个方案的好处很实际用户继续保留“看得见、挑得中、点按钮”的体验而执行动作虽然被后台化但状态可追踪、失败可重跑。系统不会因为用户在界面上点了按钮就卡住 UI大批量数据处理也没必要挤在用户会话里等。4.2 同一程序拆成两个技术出口的复用思路拆程序时最忌讳的是把老逻辑复制两份一份给 Fiori一份给 Job。复制出来的代码日后百分之百会对不上。我的做法是把业务逻辑提炼成独立的“服务类”或者基于 OO 的处理类。Fiori 的行为方法和 Application Job 的EXECUTE_JOB方法都只负责参数转换和调用这个服务类。比如上面说的订单状态穿梭我先写一个zcl_order_status_changer里面只有change_status( order_uuids, new_status )一个方法。Fiori 按钮点下去行为实现调用它后台定时批处理作业类里也调用它。逻辑只有一份两边只是触发方式不同。这个复用思路听起来简单但很多项目在改造时光顾着“让前端有界面、让后台有作业”反而把核心逻辑复制成了两份。等第二个月对账时发现Fiori 的行为和后台 Job 的状态判断不一致又回炉重造那才是真的进退两难。4.3 迁移节奏与验证方法最后聊聊节奏。老代码重新开发不能一次性切换我的建议分四步。第一步盘点。根据程序使用频率、复杂度、逻辑耦合度列一个迁移矩阵先挑高频、低风险、业务边界清晰的程序做试点也就是“先摘容易摘的果子”。第二步改造。按前两章描述的模型开发新程序同时把老逻辑保留在版本管理里别急着删。第三步并行。Fiori 应用和 Application Job 上线后和老 Report 并行运行一个业务周期拿两边的结果做核对。第四步下线。确认新程序长时间稳定、业务数据一致之后再把老 Report 从权限角色里移除。并行验证很重要。我见过有些项目新 Fiori 一上线就把老 Report 停了结果因为 CDS 视图里少加了一个状态过滤条件导致列表里的数据口径和以前完全不一致业务部门直接炸锅。多留一个并行周期成本远比出了事故再补救低。5. 改造路上最常见的四个坑最后把这些年实际踩过的、以及在项目中帮别人填过的坑集中列一列。很多问题单看技术文档不容易遇到但一上生产就全冒出来了。5.1 前台永远只有 error report 怎么办新开发的 Fiori 应用前端偶尔会弹出一个 error report 后面跟着一堆技术堆栈用户完全看不懂最典型的原因就是你后端的异常没有转成可读消息并原样抛了出去。ABAP Cloud 里正确的做法是在行为方法或服务类中显式捕获系统异常然后抛出业务异常。业务异常要继承一个带IF_T100_MESSAGE这类消息机制的异常类或者在你自己的自定义异常里定义消息文本。这样前端拿到的就不是堆栈而是一句“订单状态不合法请先确认后再提交”。有几次我就是偷懒在方法里直接RAISE EXCEPTION TYPE cx_sy_arithmetic_error前端果然就显示原始错误报告。后来统一封装了一个业务异常工具类把text、code、operation都揉进去前端体验才正常。记住你不给用户一个 user-friendly information系统就会给他一个 user-hostile technical dump。5.2 自定义模型激活后 Fiori 列表是空的这个坑也常见。CDS 自定义模型激活完全正常服务绑定也建好了结果 Fiori Elements 预览一开列表空空如也接口返回 0 条记录。首查权限。AccessControl.authorizationCheck: #CHECK加上之后如果当前测试用户没有对应角色或数据权限查询结果就是空的。这属于最容易被忽略的问题毕竟你在 ABAP 里跑 SELECT 是有全库权限的而 OData 服务会严格执行权限检查。次查关联条件。自定义模型如果 join 了很多张表某张关联表的条件写反或者状态值不一致也会导致数据被过滤光。我处理过一个 c_fiori 开头的自定义模型字段都正确但底层关联条件用了老代码里的等价字段激活后 OData 调用一直查不出数。把关联条件换回标准字段之后前台一秒出数据。最后再检查服务绑定是否更新。CDS 视图每次修改并重新激活之后服务绑定里绑定的模型版本如果没有执行“重新激活/同步”消费端拿到可能还是旧元数据。做开发时改模型记得去服务绑定里刷新一次再测。5.3 Application Job 状态绿了但业务没跑这个坑比 Fiori 空列表更隐蔽。作业调度页面上显示成功、状态是绿色但业务数据没有任何变化。问题往往出在“异常被吞”了。老代码里写满了MESSAGE ... TYPE X或者某些异常直接CATCH ... CLEANUP在桌面端你还能在输出里看到报错但进入 Application Job 后这些零散消息如果不显式写入作业消息列表运行结束就悄无声息。解决思路是在EXECUTE_JOB方法里把业务上所有需要暴露的消息收集起来通过接口的消息参数回传给 Application Job 框架。这样作业运行灯会根据消息级别变黄或者变红并显示出具体的业务信息。要养成一个习惯作业类里每个分支都应该有明确的消息返回别用“反正框架会统一记日志”来安慰自己。你不写消息框架真的一个日志都不会帮你写。还有一个和它配套的坑一个作业类被多个模板复用某个模板改了参数默认值结果其他模板跟着变。这是参数结构设计的问题。调试时发现参数不对检查模板引用而不是业务代码往往一分钟定位。5.4 老代码不能删也不能跑时的过渡方案说实话也不是所有老 Report 都能立刻改造完。有些程序逻辑极度复杂细枝末节盘根错节业务方又说“一年只跑两次但必须准”。这种情况我的处理经验是保留老 Report 逻辑作为对照基准但把所有新入口做在 Fiori 和 Application Job 侧。老 Report 可以留在版本管理里继续跑但这仅限 On-Premise 或已经开放了兼容范围的 S/4HANA。对于严格要求 ABAP Cloud 的场景老代码如果无法通过严格检查就不要硬塞进新系统更不能在生产环境里频繁修改。把它锁在历史版本里记录清楚它负责过的业务口径然后在新模型里重新建模。这种“新旧共存”虽然不完美却是项目里最务实的过渡状态。真正重要的是把改造的优先级排清楚高频、核心、结合业务价值的程序优先边缘、低频、逻辑混乱的程序可以往后放但别让它们成为新体系里的“技术债源头”。我个人在实际操作中的体会是ABAP Cloud 转型最大的阻力不在语法而在思维。以前写程序的人天然默认“报表要有一个输出界面批量要有一个变式调度”这套惯性沿用了几十年。真到动手迁移的时候你会发现只要把“界面”和“动作”分开把“查询模型”和“作业参数”彻底解耦老代码里的绝大部分逻辑都能在新框架里找到位置。最后再分享一个小技巧改造过程中坚持每个新应用都配一个相同的“业务对照样例”一组完全相同的数据老报表跑一次新 Fiori 跑一次新 Job 跑一次三次输出对得上你再拍板切生产基本就不会翻车。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进