ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SAP第二代增强:从函数模块到可配置架构的演进与实践

SAP第二代增强:从函数模块到可配置架构的演进与实践 1. 项目缘起从“打补丁”到“搭积木”的演进在ERP这类大型企业应用软件的定制化开发中我们经常会遇到一个经典难题标准功能无法完全满足某个特定客户的独特业务流程。十多年前当我第一次接触SAP系统时面对这种需求前辈们最常提到的解决方案就是“Customer Exit”。你可以把它理解成软件厂商在标准代码里预留的“后门”或“挂钩点”允许我们在不修改标准代码的前提下注入自己的逻辑。这就像在一栋精装修的房子里厂商预先在某些墙壁上留好了插座和接线盒你要加装一盏壁灯直接从这里接线就行不用去砸墙破线。传统的Customer Exit第一代主要形式是函数模块出口Function Module Exits。SAP会在关键的业务逻辑处比如创建销售订单VA01或过账物料凭证MIGO时调用一个预定义的函数模块。如果这个函数模块是空的就按标准流程走如果我们在这个函数模块里写了代码我们的逻辑就会被执行。这种方法在过去十几年里是绝对的主流它保护了标准代码的纯洁性也给了我们足够的灵活性。但做得多了痛点也来了。首先定位难。一个复杂的业务事务可能涉及几十个出口你得像侦探一样在庞大的系统里找到对的那个“挂钩点”。其次管理乱。不同顾问写的出口代码风格各异注释不全几年后连自己都看不懂更别说交接给别人。最重要的是复用性差。为A客户写的某个增强逻辑明明B客户也能用但因为代码和具体的出口函数、业务场景耦合太深很难剥离出来复用每次都得重新开发。于是“基于函数模块的第二代增强”这个概念与其说是一个全新的技术不如说是一种针对上述痛点的架构思想和最佳实践的集合。它的核心目标是把原来那种散落各处、难以管理的“补丁式”增强转变为模块化、可配置、易复用的“积木式”增强。接下来我就结合自己的实战经验拆解一下这套思路的具体实现和背后的设计逻辑。2. 第二代增强的核心设计哲学分离、抽象与配置为什么叫“第二代”它并不是SAP官方推出的一个叫“Customer Exit 2.0”的新事物而是社区和资深顾问们在长期实践中总结出的一套方法论。其核心是三个关键词业务逻辑与出口点分离、逻辑抽象化、行为配置化。2.1 告别“一地鸡毛”业务逻辑与出口点的解耦在第一代模式里我们的代码直接写在SAP提供的出口函数模块如EXIT_SAPLV60B_001里。业务逻辑和这个特定的、技术性的出口点死死绑定在一起。这导致了几个问题测试困难你无法单独单元测试你的业务逻辑必须触发整个复杂的业务事务如运行VA01才能走到这个出口点。逻辑复用是天方夜谭销售订单的增强逻辑几乎不可能直接用到交货单上尽管它们的业务规则可能相似。代码臃肿一个出口函数里可能混杂了权限检查、数据校验、字段派生、数据更新等多种逻辑可读性极差。第二代增强的做法是在出口函数里只做一件事——调用一个独立的、我们自己编写的业务逻辑函数模块或类方法。这个被调用的模块才是我们真正业务逻辑的载体。举个例子假设我们需要在创建销售订单时根据客户组自动确定一个内部审批人。第一代写法直接在出口函数EXIT_SAPLV60B_001里写一段代码读取销售订单抬头数据根据VBAK-KUNRG找到客户主数据再根据客户组决定审批人最后填回VBAK-ZZAPPROVER。第二代写法创建一个独立的函数模块例如Z_DETERMINE_SO_APPROVER。它的接口明确输入销售订单号或抬头结构输出审批人ID。在出口函数EXIT_SAPLV60B_001里只有一行有效代码CALL FUNCTION ‘Z_DETERMINE_SO_APPROVER’。所有复杂的判断逻辑、表关联查询都封装在Z_DETERMINE_SO_APPROVER内部。这样做Z_DETERMINE_SO_APPROVER这个模块就与“销售订单创建出口”这个具体场景解耦了。理论上只要我能提供销售订单号在任何地方报表、增强、甚至另一个出口我都可以调用这个模块来获取审批人。这就为逻辑复用打开了第一扇门。2.2 从“写死”到“活参”抽象化与参数设计解耦之后下一步是让我们的业务逻辑模块变得更“聪明”和通用即抽象化。关键在于函数模块的接口参数设计。一个设计良好的第二代增强函数模块其参数应该具备以下特征明确的输入/输出分区使用IMPORTING,EXPORTING,CHANGING,TABLES参数清晰区分数据流向。使用标准或统一的数据结构尽量使用数据字典DDIC中定义的结构或表类型作为参数而不是一堆分散的字段。例如传入整个订单抬头结构VBAK而不是单独传入VBELN,KUNRG,AUART等字段。这提高了接口的稳定性和可读性。设计“控制参数”这是抽象化的精髓。增加一个IV_FLAG或IS_CONTROL类型的控制参数用来决定模块内部的行为。让我们深化上面的例子。Z_DETERMINE_SO_APPROVER最初只是根据客户组找审批人。后来业务说对于特定销售部门VBAK-VKORG的订单要走另一套审批规则。按照旧思路我们得去修改这个函数模块的内部逻辑加IF...ELSEIF。在第二代增强思维下我们会这样做定义一个结构ZS_APPROVAL_CTRL包含字段RULE_ID规则IDDEPARTMENT部门CUSTOMER_GROUP客户组等。创建一个配置表ZTAPPROVAL_RULE用来维护不同部门、客户组对应的RULE_ID和具体的审批人确定逻辑甚至可以关联到一个可执行程序或另一个函数。修改Z_DETERMINE_SO_APPROVER使其接收一个IS_CONTROL参数类型为ZS_APPROVAL_CTRL。函数内部的主要逻辑变为根据传入的订单数据部门、客户组去配置表ZTAPPROVAL_RULE查询到对应的RULE_ID和执行逻辑然后动态调用相应的逻辑单元。这样一来当业务规则再次变更例如增加产品组的判断我们不需要修改Z_DETERMINE_SO_APPROVER的函数代码只需要扩展配置表ZTAPPROVAL_RULE的字段和对应的逻辑单元即可。业务顾问甚至可以在一定程度上通过维护配置表来调整系统行为无需开发介入。这就是“配置化”带来的巨大优势。2.3 中枢调度引入增强管理控制台当这样的独立增强模块越来越多时新的管理问题出现了哪个业务事务在哪个环节调用了哪些增强模块它们的执行顺序是什么如何统一开关某个增强为此一个常见的第二代增强实践是引入一个“增强管理控制台”或“调度中心”。这通常是一个中心化的函数模块或类例如Z_ENHANCEMENT_MANAGER。它的职责包括注册与管理所有遵循第二代规范的增强模块都需要在一个中央注册表如自定义表ZTENH_REGISTRY中注册。记录模块名、描述、所属业务对象、增强点、是否激活等。统一调用在各个原始的Customer Exit函数中不再直接调用具体的业务模块而是调用这个管理器。例如“ 在出口函数 EXIT_SAPLV60B_001 中 DATA: lt_enhancements TYPE TABLE OF ztenh_registry. CALL FUNCTION ‘Z_ENHANCEMENT_MANAGER’ EXPORTING iv_object ‘SALES_ORDER’ iv_event ‘BEFORE_SAVE’ CHANGING cs_order_header vbak.调度与执行管理器根据传入的业务对象和事件查询注册表找到所有已激活的、针对此对象和事件的增强模块并按预设顺序依次执行它们。提供公共服务管理器可以为所有增强模块提供统一的日志记录、错误处理、性能监控和调试开关。通过这个调度中心我们实现了增强的“可插拔”架构。要禁用某个增强只需在配置表里将其状态改为未激活无需修改任何出口代码。要调整执行顺序只需修改注册表中的序号字段。这极大地提升了系统定制化部分的可维护性和可观测性。3. 实战构建一个完整的第二代增强案例让我们通过一个更复杂的场景串联起上述所有概念。需求是在物料凭证过账MIGO时需要根据移动类型MSEG-BWART、工厂和物料执行一个自定义的库存状态校验并根据校验结果决定是否允许过账同时可能更新一个自定义的库存分析表。3.1 第一步定义数据结构与配置表首先脱离具体编码从设计开始。定义控制结构ZS_MM_GOODSMVT_CTRL。包含关键字段移动类型、工厂、物料号、校验规则ID、是否强制阻断等。定义增强消息结构ZS_MM_ENH_MSG。用于增强模块向管理器返回消息错误、警告、信息。创建配置表ZTMM_STOCK_CHECK_RULE。字段包括规则ID、移动类型、工厂、物料范围、激活状态、对应的增强函数模块名、执行顺序、错误处理级别E错误/W警告/I信息等。3.2 第二步实现独立的业务逻辑模块创建函数模块Z_STOCK_CHECK_CUSTOM。接口设计IMPORTING:is_controlTYPEZS_MM_GOODSMVT_CTRL,it_msegTYPETABLES(MSEG表)EXPORTING:et_messagesTYPEZT_MM_ENH_MSG(消息表),ev_blockedTYPEABAP_BOOLCHANGING:cs_mkpfTYPEMKPF(可选如需更新凭证抬头)内部逻辑根据传入的is_control中的规则ID或根据移动类型/工厂/物料动态匹配配置表确定本次执行的具体校验逻辑。执行校验例如检查特定库存状态是否满足移动条件。这里的校验逻辑本身也可以进一步模块化。根据校验结果填充et_messages和ev_blocked。可选如果需要更新自定义表在此函数中处理确保事务一致性。这个函数模块是完全独立的。我们可以为其编写独立的单元测试传入各种测试数据验证其校验逻辑是否正确而无需启动整个MIGO事务。3.3 第三步实现增强管理器创建函数模块Z_ENH_MANAGER_MM。接口设计与业务场景强相关。例如针对MIGO的某个特定出口IMPORTING:iv_event(如 ‘BEFORE_POST’)CHANGING:ct_msegTYPETABLES,cs_mkpfTYPEMKPFEXPORTING:et_overall_messagesTYPEZT_MM_ENH_MSG,ev_processing_blockedTYPEABAP_BOOL内部逻辑根据iv_event和业务对象‘MATERIAL_DOCUMENT’查询ZTENH_REGISTRY表获取所有需要执行的增强模块列表按顺序。循环这些模块动态调用CALL FUNCTION func_name IN BACKGROUND TASK或使用CALL FUNCTION func_name。收集每个子模块返回的消息和阻断标志。应用统一的冲突解决策略例如任一子模块返回错误则整体阻断警告和信息只收集展示。将整合后的消息和最终阻断标志返回。3.4 第四步在标准出口中注入调用找到MIGO过账前合适的Customer Exit例如MB_DOCUMENT_BEFORE_UPDATE函数组中的某个出口。在该出口函数中编写如下代码DATA: lt_messages TYPE zt_mm_enh_msg, lv_blocked TYPE abap_bool. CALL FUNCTION ‘Z_ENH_MANAGER_MM’ EXPORTING iv_event ‘BEFORE_POST’ CHANGING ct_mseg it_mseg “ 假设出口提供了MSEG内表 cs_mkpf cs_mkpf “ 假设出口提供了MKPF结构 IMPORTING et_overall_messages lt_messages ev_processing_blocked lv_blocked. IF lv_blocked abap_true. “ 将 lt_messages 中的错误信息显示给用户或抛出异常阻止过账 MESSAGE e000(zmm) WITH ‘自定义库存校验失败’. ENDIF.至此一个完整的第二代增强流程就构建完毕了。我们可以看到标准出口里的代码非常简洁和稳定几乎不需要再改动。所有业务逻辑的变化都被封装在独立的模块Z_STOCK_CHECK_CUSTOM和配置表ZTMM_STOCK_CHECK_RULE中。4. 关键实施要点与避坑指南理念虽好但落地时细节决定成败。以下是我在多个项目中实践这套方法总结出的关键点和常见陷阱。4.1 性能考量动态调用与批量处理的权衡动态调用函数模块CALL FUNCTION func_name会带来微小的性能开销。在循环次数极多如处理万行级别的行项目的场景下需要谨慎。优化建议1批量处理确保你的增强管理器设计是支持批量数据传入的如传入整个MSEG内表而不是在行项目循环内部调用管理器。让管理器内部去循环处理或者让业务逻辑模块自己处理批量数据。优化建议2缓存配置增强管理器在查询增强注册表或配置表时应考虑使用应用层缓存如CL_SHARED_MEMORY_AREA避免每次执行都重复读取数据库。优化建议3选择性执行在管理器中可以根据输入参数快速判断是否需要执行后续增强模块。例如如果移动类型不是我们关心的可以直接跳过查询和调用。4.2 错误处理与消息管理的统一范式多个增强模块可能都会产生消息错误、警告。如何统一收集、排序并呈现给用户必须建立统一的消息结构如前所述的ZS_MM_ENH_MSG应包含消息类型E/W/I/S、消息ID、消息编号、消息变量、以及可能的消息目标如具体到哪个行项目号。管理器负责聚合与去重管理器需要具备基本的消息聚合能力并按照一定的优先级如错误优先排序。与标准消息的集成最优雅的方式是将自定义消息通过MESSAGE ... RAISING语句或者BAPIRETURN结构集成到标准流程中。但在Customer Exit中有时可能需要更直接的方式比如直接调用MESSAGE ... TYPE ‘E’来终止事务。这需要与出口的具体上下文结合没有银弹。我的经验是尽量使用出口提供的CHANGING参数来回传错误标志和消息表让标准程序去决定如何展示。4.3 调试与监控让增强变得透明第二代增强因为引入了中间层调试链路变长。必须建立有效的调试和监控手段。设计时注入日志点在增强管理器和关键业务模块中使用APPLICATION_LOG或自定义日志表记录关键节点的输入、输出和决策过程。务必提供一个开关如自定义开关表或内存ID来控制日志的详细级别在生产环境可以关闭详细日志以避免性能问题。使用统一的调试开关可以通过一个全局的调试标识如cl_demo_outputdisplay包装在条件语句中或写入一个共享内存区域在测试时一键开启所有相关增强的详细输出。事务代码支持可以考虑开发一个简单的报表ZENH_MONITOR用于查看最近执行的增强调用记录、性能数据和错误信息这对于运维阶段排查问题至关重要。4.4 版本控制与传输的协同自定义函数模块、DDIC对象结构、表、配置表这些构成了一个增强“特性包”。它们必须作为一个整体进行版本控制和传输。使用传输请求包确保所有相关对象放在同一个传输请求中并写好详细的描述。配置表的传输配置表ZT*的内容通常属于客户化配置其传输需要特别小心。要明确哪些是开发/测试环境配置哪些是生产环境必须的初始配置。通常表结构随开发请求传输而具体配置数据可能需要通过后续的配置传输请求TypeCUST或使用SCC1客户端拷贝来处理。文档化为每个增强“特性包”编写简要的设计文档说明其目的、主要模块、配置表含义和关键处理逻辑。这份文档应该和代码一起存放于版本库或知识管理平台。从“打补丁”到“搭积木”基于函数模块的第二代增强思路本质上是一次针对企业软件定制化开发的微观架构升级。它通过分离关注点、抽象业务逻辑和集中调度管理显著提升了代码的可维护性、可测试性和可复用性。实施这套方法需要前期更多的设计和抽象思考但带来的长期收益——降低维护成本、快速响应业务变化、提升团队协作效率——是传统方式无法比拟的。当你面对下一个增强需求时不妨先停下来不要急于钻进SE80写代码而是花点时间思考这个逻辑能否独立成一个模块它的输入输出是否清晰未来会不会有类似的需求也许这就是你代码质量提升的一个新起点。
返回列表