ARTICLE DETAIL

资讯详情

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

企业 Workflow 如何接入 ERP、CRM?从 API 调用到业务契约

企业 Workflow 如何接入 ERP、CRM?从 API 调用到业务契约 企业 Workflow 如何接入 ERP、CRM从 API 调用到业务契约AcmeFlow 的运营审核通过后系统要从 CRM 找到客户与套餐再向 ERP 创建一条维保服务记录。开发者很容易把这件事写成两次 HTTP 调用先 GET 客户再 POST 服务记录。演示时两个接口都返回 200流程往下走似乎已经完成集成。真正困难的情形发生在第二次调用ERP 已经提交记录但返回报文在网络中丢失。我们的客户端只看到 RemoteDisconnected。如果此时简单重试 POST可能建立第二条服务记录如果把流程标为失败又会把已经存在的服务当成不存在。这篇把“接上 API”转化为“建立业务契约”。适配器必须知道谁是客户主数据来源哪些字段能映射什么身份把远端记录和本申请绑在一起远端对重复请求有什么承诺以及响应未知时如何查询。代码用 Python 标准库启动两个本机模拟端点CRM 返回客户信息ERP 在收到创建请求后先把记录写入内存再故意断开连接。适配器捕获异常按稳定 external_ref 查询 ERP找到原来的 ERP-001最终断言远端记录数仍为一。实验使用真实 loopback HTTP但不是接入真实企业系统也没有生产鉴权。一、把流程语言和接口语言分开在流程视角AcmeFlow 关心的是“这份申请是否已有一条可核实的 ERP 服务记录”。在 ERP 视角接口可能要求 customer_no、plan_code、service_start_date 等字段在 CRM 视角客户叫 customerNo套餐叫 servicePlan。若 Workflow 代码直接拼每个远端 JSON远端改字段时流程定义也要跟着改重放兼容与业务规则被协议细节绑在一起。更稳妥的边界是适配器Workflow 发出“为这份申请创建服务记录”的业务请求适配器负责查主数据、翻译字段、设置关联键、解释错误、查询结果最终返回 CREATED、UNKNOWN 或 REJECTED 这样的业务可用结论。这不是为了多写一个类。真正的防腐层应承担明确约束而不是把 requests.post 重新包装成 erp_client.post。它要拒绝未支持的套餐避免错误字段被静默填成默认值要把远端服务记录的唯一身份连到 tenant_id、application_id 和 material_version要让流程知道“创建结果未知”与“ERP 明确拒绝”不同。图 1 展示三个层次。示例里的 CRM、ERP 都是本地模拟服务便于复现断连窗口企业落地时需要根据真实接口文档确认字段所有权与幂等保证不能照搬样例字段名。图 1教学架构图。流程只消费业务结果协议、映射和查询对账封装在适配器内。本篇继续沿用系列数据契约。application_id 是完整 UUIDtenant_id 为固定教学租户 xinghe-demobusiness_key 是给人看的 APP-ERP-013资料版本为 v1。适配器构造 external_ref 时把租户、申请 UUID、资料版本和服务动作拼在一起形成一个稳定的业务关联键。它与 business_key 不能混为一谈业务键用于客服、销售和日志搜索可能受编号规则影响external_ref 用来辨识“这一次创建服务记录动作”即使重试也不能改变。一个申请将来若有续费、升级套餐等不同动作还需要不同的动作标识不能把所有写入都压在同一键上。二、字段映射先问所有权再谈格式CRM 是客户主数据来源提供 customerNo、legalName、servicePlan。ERP 创建服务记录需要 customer_no 与 plan_code。最简单的映射看起来只是把驼峰变成下划线但企业系统里同名字段未必同义。一个系统的 servicePlan 可能是销售套餐另一个系统的 plan_code 可能是收费项目两者之间需要产品与财务确认的映射表客户法定名称可能允许 CRM 修改但已签合同上的主体名称不能在创建服务时被最新版 CRM 值悄悄覆盖。字段映射要连同来源、版本、允许值和责任人记录代码只执行已经约定的规则。样例脚本只接受 MAINTENANCE_BASIC 套餐查不到或读到不支持的套餐就拒绝继续它不尝试猜测一个相近的 ERP 编码。对于真实系统映射还要处理空值、停用客户、地区、税号、合同主体、金额与币种以及字段何时可以更新。如果 CRM 请求成功而 ERP 请求失败CRM 查询结果是否需要快照如果等待期间客户套餐被销售修改当前申请应沿用提交时选择还是重新审核这些决定影响流程版本和审批有效性不是序列化库能替团队决定的。图 2字段映射示意。每个映射都应附来源和约束而不只是目标字段名。接口认证也属于契约的一部分。本机脚本没有 OAuth、服务账号或签名因为它只绑定 127.0.0.1 的模拟 HTTP 服务。真实对接应先确认 ERP/CRM 的身份机制、令牌受众、最小权限、密钥轮换和审计日志流程实例不能把长期凭据放进事件历史或日志。访问客户主数据与创建服务记录可能需要不同权限适配器应以该动作所需的身份调用而不能使用一个万能管理员令牌。若是多租户场景tenant_id 更不能只作为日志标签它必须参与客户查询、关联键构造、远端授权与结果核对。本系列当前固定一个教学租户没有宣称已实现真实租户隔离。三、一次断连到底意味着什么按照 HTTP 语义标准 RFC 9110 的幂等章节客户端在未读到响应前不能仅凭连接关闭认定非幂等 POST 未被服务器处理。标准区分请求方法的幂等语义并提醒不要在不知道语义安全的情况下自动重试非幂等请求。创建 ERP 服务记录通常是有业务副作用的 POST如果远端没有承诺相同业务键只创建一次自动重发就是风险。网络错误描述的是客户端观察到的传输结果不是远端业务数据库的最终状态。示例的故障点很明确ERP 模拟服务在 do_POST 中执行 setdefault把 external_ref 对应的 ERP-001 写入内存然后关闭连接而不发送成功响应。客户端拿到 RemoteDisconnected。适配器没有立即重发 POST而是 GET /erp/service-records?external_ref…读到唯一的既有记录核对键后返回它。实际输出里第一行是 create_response_lost第二行是 reconciledremote_record_count 等于一。这个实验比单纯 mock 一个异常更有说服力因为 HTTP 请求确实到达服务端远端状态与客户端观察出现了分离。图 3教学故障时间线。客户端异常不等于 ERP 未创建先查业务事实再决定是否重试。查询对账也不是万能按钮。如果按 external_ref 查到恰好一条记录还应比较客户、套餐、申请版本和业务动作是否一致同一键下内容不一致意味着远端幂等实现或我方键使用有问题应暂停自动推进。如果查到多条不能任选第一条否则可能给错误客户开通权益。如果查到零条也不能立即推断“之前没成功”远端查询索引可能延迟、读写可能跨区域或者记录尚未对查询接口可见。应按远端合同约定重试查询达到预算后交给人工对账只有在明确安全的条件下才以同一键重发创建请求。四、让 UNKNOWN 成为一等业务状态很多系统只有 success 和 failed 两种返回。它们会把超时、连接断开、服务端返回 503、明确参数拒绝都塞进 failed随后调度器统一重试。跨系统写操作至少要区分 CREATED、REJECTED 和 UNKNOWN。CREATED 表示拿到了可核对的远端凭据可以考虑进入权益开通REJECTED 表示远端明确说这个请求不合法应记录错误原因并让责任人修正UNKNOWN 表示客户端不知道远端是否完成需要查询、等待或人工处理。UNKNOWN 不是程序员不愿处理的“异常”而是真实业务世界里的一种知识状态。图 4 给出了三类查询结果。这里最重要的分支不是“查到就好”而是“查到一条且字段一致”。若 ERP 返回一条客户编号不同的记录即使 external_ref 看似匹配也不能继续开通可能是键冲突、数据污染或远端实现缺陷。若远端服务不支持按业务键查询项目验收前就应识别这个缺口约定幂等键、增加查询 API、由人工通过后台核实或者调整自动化范围。不能等到线上首次超时才发现既不能安全重试也无法调查。图 4教学决策图。查不到不自动等于失败查到多条或错配必须进入人工处置。适配器的返回对象需要足够支撑流程决策和审计。CREATED 应包含远端记录 ID、关联键、确认时间与必要的远端版本UNKNOWN 应包含最后一次请求 ID、错误分类、下次查询时机和人工出口REJECTED 应包含稳定的业务错误码、可安全展示的原因及是否允许修正后重试。不要把远端原始错误报文全文写进历史其中可能含客户隐私或认证信息。也不要只返回 HTTP 200/500因为同一个状态码在不同接口可能承载完全不同的业务含义。网络层事实和业务层结论应分别保存。图 5业务结果契约。CREATED、UNKNOWN、REJECTED 分别触发不同流程动作。五、代码结构与运行结果adapter_demo.py只有一个文件。标准库 ThreadingHTTPServer 在随机本地端口启动模拟系统GET /crm/customers/C-17 返回客户编号与套餐POST /erp/service-records 在写入 REMOTE_RECORDS 后关闭连接GET /erp/service-records 根据 external_ref 返回记录。适配器先从 CRM 获取信息检查套餐然后构造 ERP 请求。RemoteDisconnected 属于连接失败异常被捕获后进入查询对账。脚本最后断言 application_id 为本次生成的 UUIDremote_record_count 为一返回的 ERP 记录仍是 ERP-001。它没有调用真实 SaaS 账户也不要求开发者提供生产密钥。把结果未知分支写成代码时核心是“查回已有记录”不是盲目补发写请求try:returnrequest_json(base/erp/service-records,erp_payload)except(URLError,ConnectionError,OSError)asexc:recordsrequest_json(base/erp/service-records?external_refexternal_ref)[records]iflen(records)!1:raiseRuntimeError(outcome still unknown; hand off to reconciliation)fromexcreturnrecords[0]这个脚本用远端内存字典的 setdefault 模拟按 external_ref 的唯一性故障注入只覆盖一次“先提交后断连”。本机实测输出的申请 UUID 会每次变化可读 business_key 保持 APP-ERP-013。读者复跑时应关注事件顺序与计数不要把某一次 UUID 当作固定测试数据。模拟远端是单进程、无认证、无持久磁盘也没有并发请求条件下的强一致实现真实 ERP 是否支持稳定查询与唯一约束必须通过它自己的接口合同或联调验证。图 6 对应的“实测”只指本地 loopback HTTP。图 6实验结果图。真正发出了 HTTP 请求并遭遇断连没有验证真实企业接口。若准备把示例改成 FastAPI Activity仍应保留相同的业务顺序先固定请求键再发写请求结果未知时查再决定返回 CREATED 还是 UNKNOWN。不能因为换了 HTTP 客户端库就让自动重试中间件先于业务判断执行。尤其要检查客户端是否默认重试 POST、反向代理是否会在断连时重发以及任务队列是否会因为 ACK 丢失再次分配同一 Activity。调用链上任意一层重试都可能改变“远端收到几次”的事实稳定业务键和结果查询是跨层保护而不是某个 SDK 的选项。六、验收接口契约而不是只验收接口可达联调清单应从正常路径扩展到故障路径。正常路径要核对 CRM 客户与套餐映射、ERP 记录的关联键、返回 ID 和后续权益动作。失败路径至少包括 CRM 查不到客户、套餐不支持、ERP 明确业务拒绝、ERP 限流或服务不可用、ERP 已提交但响应丢失、查询零条、一条、多条或字段错配。每一类要定义状态、重试预算、告警、责任人和人工出口。只用 Postman 截一张 200 响应图不能证明工作流接入完成。实际交付时还要和远端团队核实接口的时间语义创建成功后多久能通过查询接口读到按哪个字段保证唯一同一 external_ref 携带不同 payload 时是返回原记录、拒绝冲突还是更新旧记录限流配额是每租户、每应用还是共享批量对账接口是否提供分页和更新时间如果这些答案没有写进双方确认的集成契约我方代码里再精妙的指数退避也可能在边界情况下出错。FDE 在客户现场的价值之一就是把这些看似“接口细节”的问题提前转成可验收的业务约束。七、external_ref 应如何设计与治理稳定关联键不是随便拼几个字符串。它要明确作用域同一租户的同一申请、同一资料版本、同一个服务创建动作重试时应得到同一个键不同申请或不同业务动作不能误用同一个键。本篇采用 tenant/application_id/v1/service 这样的教学形式目的是让读者看出四个维度。实际系统还要评估远端字段长度、允许字符、是否暴露租户标识、是否区分环境以及远端是否真的以该字段实施唯一约束。若 ERP 只保存 external_ref 但不保证唯一那么它只能帮助查询不能独自承担幂等保护。有人会问资料从 v1 改成 v2是否应该换一个 external_ref答案取决于外部服务动作的业务含义。若 v1 尚未创建记录且申请重新审核后才允许 v2 创建新版本键有利于表明依据变化若 v1 已经创建 ERP 服务记录不能因为材料升级就直接新建第二条有效记录。此时可能需要对现有记录进行更新、撤销后重建或人工确认远端是否允许修改。版本号是证明依据的线索不是绕开“同一客户只能有一条有效维保服务”的通行证。键设计要与远端生命周期一起审查。还需要防止两个不同请求错误地复用同一键。远端如果支持真正的幂等创建通常应在重复键但 payload 不同的情况下报告冲突而不是悄悄返回之前的结果。适配器拿到“已存在”也必须核对客户编号、套餐、合同与申请版本否则一个映射 bug 可能把 A 客户的记录当成 B 客户的成功结果。本地可为请求正文保存规范化摘要查询对账时与远端可见字段比对涉及隐私的字段不应在普通日志里明文传播。并发重试还要靠远端唯一约束或我方序列化机制单进程内存锁不能保护多 Worker 场景。八、错误分类决定调度策略把 HTTP 400、409、429、500 和网络断连都做成“失败”会损失关键语义。字段缺失的 400 通常需要修正请求业务冲突的 409 要看是同一 external_ref 已有一致记录还是存在不同资料的冲突429 表示限流应尊重远端提供的重试时间和配额5xx 可能暂时不可用却也可能发生在服务端写入之后连接断开则尤其不能说明远端未处理。适配器应先把协议层结果分类成业务结论再把明确可重试的故障交给执行平台。原始状态码可作为诊断字段保存但不应直接决定是否开通。限流与重试还有跨实例影响。假设同一租户有一千份申请同时进入 ERP 创建如果每个 Activity 遇到 429 都在一分钟后同时重试就会形成新的洪峰。退避需要抖动、总预算和并发限制重复请求仍需固定键。若远端限流按租户计算我方一个大客户的积压不应把其他租户的服务创建全部拖住。只有单一教学租户的本篇脚本没有实现这些调度能力因此不能用一次 loopback 请求成功推断容量足够。生产验收应在远端允许的压力边界内测试积压与恢复速度而不是压测到对方服务不可用。对 UNKNOWN 的时间预算也要约定。立即查询可能遇到读延迟每隔一秒无限查询又会消耗配额。可以定义短期自动查询窗口之后转为定时对账再超过业务截止时交给人工。每次查询保留时间、请求 ID、返回记录 ID 和比较结果便于财务与 ERP 团队共同调查。若超过期限还未确定流程应保持“结果待确认”并阻止权益自动开通把它强制改成失败会导致销售反复提交新申请把它强制改成成功又可能让客户拿到没有对应 ERP 凭据的服务。未知状态并不体面但诚实且可恢复。九、CRM 读到的资料是否需要冻结Workflow 执行可能跨越数天CRM 客户档案会变化。今天读取的 legalName和客户提交申请时合同上签署的 legalName可能不同。若审批依据的是提交时的合同主体创建 ERP 时应使用已审核的那份数据快照或可追溯版本而不是无条件重新读取最新 CRM 值。若业务规则要求使用最新客户状态则要在开通前重新校验并可能触发重审。流程代码不能简单地把“实时读取总是最新”当作正确因为“最新”与“经过批准”不是同一个概念。套餐映射也有生效期。销售在 CRM 中把客户从 BASIC 升到 PREMIUM并不意味着一份已签 BASIC 合同的申请可以直接创建 PREMIUM 服务。适配器应接收由流程确认的目标套餐与材料版本查询 CRM 时用它核实客户身份和当前状态而不是让远端字段反向覆盖已批准决策。若信息冲突返回业务拒绝或待人工处理比猜测哪个系统“更新”更安全。反过来如果 CRM 标记客户已注销虽然旧合同数据完整也可能需要阻止自动开通这些规则应由业务方明确提出适配器只执行约定检查。数据版本的处理还会影响审计。调查一条错误 ERP 记录时不能只看到当前 CRM 页面因为页面已经变化。至少要知道创建时使用了哪个 customerNo、套餐映射版本、申请资料版本和合同编号。若企业对个人信息的保留与删除有规定应在可追溯性与最小化之间设计保存必要的稳定标识和证据引用敏感正文留在有权限的主数据系统不把全文复制进每个日志和执行历史。这样既能解释当时的决定又不会让集成层成为未经治理的数据副本。十、观察一次调用要同时看三本账第一本账是 AcmeFlow 的过程账申请在什么状态哪个 Activity 发起当前是 CREATED、UNKNOWN 还是 REJECTED下一次对账何时进行。第二本账是远端业务账CRM 客户是否存在ERP external_ref 下有几条服务记录哪条有效权益平台是否又执行了后续动作。第三本账是传输账HTTP 请求 ID、响应码、延迟、超时或断连发生在什么时候。三本账互相验证只看其中一本容易把“客户端失败”误当成“业务失败”。日志中推荐使用同一组稳定关联字段tenant_id、application_id、business_key、external_ref、trace_id 与 attempt_id。前四个用于业务核对trace_id 连接一次调用链attempt_id 区分同一业务动作的多次网络尝试。不要把 request_id 和 business_key 当成一回事一次业务动作可以有多次网络尝试但只能有一份被认定的业务结果。监控里需要统计 UNKNOWN 持续时间、对账成功率、错配记录数、重复键冲突数与人工处理积压。大量成功 200 响应不能掩盖少数长期结果未知的客户申请。若发现同一 external_ref 下多条 ERP 记录处置动作应有权限与审计。运维人员先暂停该申请后续权益创建核对远端记录的状态、时间与费用再决定保留哪条或要求远端撤销。直接删除本地一条日志或者把 application.state 改成 ACTIVE会让另一条远端记录继续存在也会抹掉事故线索。可修复性不是提供一个任意修改状态的后台按钮而是让每一步修复都有对象版本、操作者、理由与结果。这个要求与前面 Saga 补偿和第 16 篇运行现场的思路相连。十一、从模拟服务走向真实系统的交付顺序开始联调前先拿到接口所有者、环境、认证方式、字段定义、错误码、速率限制、幂等与查询能力的书面答案没有这些写完代码只是把不确定性藏在适配器里。接着用一条合成申请走通成功路径核对两边的记录与审计。然后注入可控故障客户端超时、服务端拒绝、重复请求、旧资料版本、并发两次创建以及查询延迟。与远端团队一起确认故障后双方各自看到什么而不只是我方控制台输出。最后再设定上线流量、人工值守与回滚条件。如果某项能力远端暂时无法提供例如没有按业务键查询 API可以缩小自动化范围创建前要求人工确认唯一性提交后遇到结果未知直接转人工不自动重试。这样交付速度可能下降却能避免重复创建引发收费或权益错误。FDE 的工程判断不是把所有路径自动化而是在事实可核查的地方自动推进、在缺少证据的地方停下来。等远端补齐幂等或查询能力再扩大自动化范围。这种分阶段上线通常比一次性宣布“ERP 集成完成”更容易通过客户验收。正式验收还可以安排一次“双方对账演练”由我方保存申请 UUID、external_ref 和请求时间远端团队独立从 ERP 查询同一记录双方分别记录客户编号、套餐、服务状态与创建时间再对照结果。随后故意让客户端在提交后失去响应检查远端仍能按键找回记录、我方不会生成第二条。若双方查询结果不同要先排查查询视图延迟、环境混用、权限过滤和时区转换不能立刻认定某一方数据丢失。对账演练的产物是一份可复查的事实链未来故障时能按同样步骤调查。最后要约定客户可见的进度文案。UNKNOWN 阶段页面可以写“正在核实服务开通结果”同时显示预计更新时间和客服联系方式不宜显示“创建失败请重新提交”因为远端可能已经创建。内部运维界面则需要比客户界面更详细显示远端键、查询次数、最近错误与处置负责人。把不确定性以适当粒度呈现给不同角色既避免客户重复操作也减少一线人员靠猜测回答问题。技术契约落实到用户沟通才算集成真正落地。每次修复后还应回看原先的故障分类是否准确。如果多数 UNKNOWN 最后都查到了已创建记录说明网络层或确认机制需要优化如果多次查询仍无记录可能是远端查询能力不足或键写错。把这些结论反馈给 ERP/CRM 接口所有者修改协议或映射规则再通过同样的故障实验回归而不是让人工对账永远承担系统缺陷。十二、Workflow Thinking谁拥有最终事实当 ERP 已经有一条服务记录而 AcmeFlow 仍显示“创建中”究竟哪边说了算对于“ERP 记录是否存在”这个事实ERP 是权威对于“这份申请是否满足开通条件”AcmeFlow 仍要综合审批、签署、收款和权限事实。对账不是简单地让本地 status 向 ERP 复制而是核对相同业务动作的远端证据再根据完整业务规则推进本地流程。相反若本地显示成功而 ERP 查无记录也不能因为本地页面看起来正常就忽略不一致。应暂停后续权益动作查历史与远端必要时通知人工处理。优秀的适配器最终让故障能够被解释请求的外部关联键是什么发送时使用哪版材料和映射规则客户端观察到什么错误远端查询返回什么记录为什么选择继续、重试或人工处置。它让销售、财务与客户支持可以用同一业务标识讨论问题而不是互相转发服务器日志。下一篇从单次 API 调用转向持续涌入的回调与消息付款可能先于签署消息可能重复甚至可能来自错误租户。那时要处理的不只是“有没有收到”还有“这条消息到底属于谁”。
返回列表