拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

保险核心系统重构实战:事件驱动与领域建模的金融架构解析

保险核心系统重构实战:事件驱动与领域建模的金融架构解析 去年我接手了一个保险核心系统重构项目内部代号就叫 financial-services。这名字看着宽泛但实际做下来它几乎涵盖了金融服务行业的大部分典型技术命题领域建模、事件驱动、客户数据治理、安全合规、高可用架构和可观测性。当时团队里有人说这不就是把旧系统拆成微服务吗真做起来才发现金融服务场景的约束和普通互联网项目完全不是一回事。这篇文章不是那种到处都能搜到的框架介绍而是把我在这个项目里从架构设计到落地踩坑的完整过程拆开讲。内容包括核心模块怎么划分、事件驱动在保单生命周期里怎么落地、客户主数据为什么要单独治理、审计追踪和敏感数据保护具体怎么做以及工程配套里那些决定项目成败的细节。如果你正准备做一个金融类业务系统或者想了解金融服务的参考架构在真实项目里是什么样子这篇应该能帮你省掉不少试错时间。怕字多的话可以先看章节标题挑着读但更建议从头啃完因为很多坑是连在一起的。1. 金融服务项目的架构起点先搞清楚钱和约的不同金融系统的核心难点在于它同时处理两类性质完全不同的数据一类是钱另一类是约定。在很多项目里这两者被混在一起建模系统一复杂就出问题。我们这次重构的对象是一个经营多年的保险业务系统。老系统是单体应用所有业务逻辑都写在一个巨大的代码库里保单、缴费、理赔、客户信息全在一起。表面看功能都能跑但每个需求改动都要动那几十个核心表发布一次要全量回归测试稍有不慎就把别人模块搞坏了。1.1 为什么保险业务特别适合作为金融服务重构的起点保险核心系统是最典型的金融服务项目之一因为它的业务链条完整覆盖了金融系统的所有核心场景客户管理个人客户、企业客户的准入、变更和关系维护账户与凭证管理保单作为核心资产凭证承载价值和权益资金流转保费收取、理赔支出、佣金结算涉及多条资金通道期限与状态保单有生效、宽限、失效、终止等状态状态机极其丰富审计与合规每一笔操作都要能追溯数据保留周期非常长这意味着如果你能把一个保险核心系统做好金融服务领域的大部分系统架构问题你都有经验了。反过来如果一上来就挑战银行核心账户系统那复杂度会直接劝退。所以我们选了一个折中方案不碰老系统的核心账户逻辑而是把周边链路拆出来重构先建立新的客户中心、产品中心、保单中心和理赔中心再逐步将老系统流量切换到新架构上。这个策略在行业里叫绞杀者模式Strangler Pattern对金融服务这种高风险系统来说是最稳妥的迁升路径。1.2 战略建模上我们做的第一件事重新划分领域边界开项目第一次架构评审我让团队用一张白纸画出系统的各个模块不限制技术只描述业务。大家画的都是订单、用户、支付这种互联网范式的模块。但保险业务里根本没有订单这个词最接近的是投保单而且投保单到保单之间还有核保环节核保可以自动通过、人工审核、或者拒保这个状态变化比电商订单复杂得多。我们用领域驱动设计里的限界上下文Bounded Context重新组织了模块边界核心划分是限界上下文职责范围关键实体客户中心客户准入、身份验证、客户关系管理Customer, CustomerRelation产品中心产品定义、费率配置、条款管理Product, Coverage, RateTable投保中心投保信息采集、核保、承保Application, UnderwritingDecision保单中心保单生命周期管理、保全、续期Policy, Endorsement, PolicyStatus收付费中心保费收取、理赔支付、账务核对Payment, PremiumTransaction, LedgerEntry理赔中心报案、立案、核定、理算、赔付Claim, ClaimAssessment, BenefitCalculation这个边界的核心洞察在于保单中心和收付费中心的分离是保险系统最重要的边界。保单管的是约定收付费管的是钱。逻辑上两者有交互但在架构上不能耦合在一起否则任何资金变动都要锁保单系统的并发能力就直接塌了。之前老系统最大的问题就在这保单表里直接存账户流水保单一冻结所有缴费都停了销售渠道稍微一搞活动数据库并发马上打满。边界重新划分之后我们没有动老的功能只是先给新模块之间的数据交互定义了契约这为后续的事件驱动埋下了伏笔。2. 保单状态机是系统的骨架状态建模错了后面全是补丁保单这个领域实体在保险系统里的地位相当于银行系统的账户。保单状态建模的精细程度直接决定业务规则的复杂度。如果你把状态建模成简单的有效/无效后面每接一个渠道、每做一个保全功能都会死得很难看。2.1 实际项目里保单状态是怎么划分的在我们梳理老系统时发现状态字段本身没什么问题问题在于状态是通过硬编码的if-else散落在各个子模块里的。A模块判断一套状态集合B模块又用另一套没有一个统一的状态机定义。结果是同一个保单在缴费模块叫效力终止在理赔模块叫失效客服查不到记录开发排查要翻代码。新系统里我们定义了一套覆盖保单全生命周期的状态模型包含九大类状态已创建Created待生效Pending Activation有效Active宽限期Grace Period自动垫缴Premium Loan效力中止Lapsed复效申请中Reinstatement Pending终止Terminated已失效Expired状态变化不是随意跳转的而是通过事件驱动转移。比如从有效进入宽限期只能由缴费未成功事件触发从宽限期恢复到有效只能由成功缴费事件触发。我们在代码里用一个状态机配置表来管理所有合法流转路径而不是把规则散落在每个业务方法里。2.2 状态机建模过程中最容易忽视的两类细节第一类细节是时间维度的状态变化。保单状态很多不是操作出来的而是时间到了自动发生的。比如过了宽限期还没缴费保单就从宽限期进入效力中止。老系统靠定时任务去扫描更新数据量大之后扫描效率低而且半夜批量更新容易出错。新系统里我们把这些时间点建模成延期事件Delayed Event在事件总线上通过延时消息触发状态流转准确度和资源占用都改善了不少。第二类是明细状态和汇总状态的拆分。保单需要一个对外展示的汇总状态比如有效、失效但在内部还需要一个更细的状态来描述处于什么子阶段。比如有效状态下收益人变更审批中、贷款还款中、减额缴清申请中等。这类子状态不进主表而是作为保单明细的标签存在避免主状态字段被塞进一堆临时标记。我个人的体会是状态机配置表一定要做成可视化可查的。项目上线一个月后业务方会频繁申请增加新的状态流转路径如果没有清晰的配置界面开发就只能改代码重新发版。我们后来把状态机配置做进了管理后台产品经理自己就能配置流转规则开发只需要维护事件处理器。3. 事件驱动架构在金融服务里的实体落地以保单生命周期为例架构分层之后模块之间的数据一致性就成了最大的挑战。保单中心要通知收付费中心这张保单已经承保了可以开始收费收付费中心要通知保单中心这笔保费已经到账传统做法是同步调用接口但这样模块之间就产生了时序耦合。我们最终选择了事件驱动架构而且不是赶时髦是因为金融服务场景里很多动作天然就是异步的。3.1 为什么金融服务事件不能只发消息还要考虑可靠性和幂等事件驱动在互联网项目里很常见但金融服务场景有几个特有的约束决定了你不能直接把Kafka拿来用就完了。第一个是事件不丢。保单承保事件要是丢了收付费中心就永远不会发起扣款对业务来说直接导致应收保费长期挂账。所以我们在事件发布端和消费端都做了确认机制消息发布后必须等Broker确认消费端处理完成后须手动提交offset消费失败自动重试重试超过上限进入死信队列待人工处理。第二个是消费端必须幂等。Kafka或RocketMQ的投递语义是至少一次同一个事件在极端情况下可能被投递两次。金融服务里扣了两次保费这种事是绝对不允许的。每个事件消息都带一个全局唯一的事件ID消费端基于事件ID做去重存储。我们用的是持久化的去重表在消费事务里先查ID是否存在存在则直接跳过不存在则执行业务逻辑并写入去重表。第三个是事件的时序性。同一个保单产生的事件在消费端必须按产生顺序处理。我们以保单号作为分区键确保同一张保单的事件进入同一个分区消费端单线程按顺序消费。这是比较笨但也最有效的做法。3.2 事件定义里的细颗粒度拆分直接影响后续的扩展成本事件名称的颗粒度很有讲究。如果只定义一个保单变更事件消费方拿到事件后还得自己解析变更内容事件就退化成了一种传输格式而不是业务语义。我们在项目里把事件按业务动作拆到足够细比如PolicyIssued保单承保PremiumPaid保费入账PremiumPaymentFailed缴费失败PolicyEnteredGracePeriod保单进入宽限期UnderwritingApproved核保通过ClaimSubmitted理赔报案BenefitPaid理赔金支付每个事件都包含该业务动作的完整上下文。比如PremiumPaid除了保单号、金额还带上渠道、支付流水号、收付费中心内部的账务凭证号。这样消费方拿到事件后不需要再反向查询其他系统的接口减少了一次跨模块的同步调用。3.3 事件溯源在这个项目里的使用边界当时团队里有人提议用事件溯源Event Sourcing来做保单表所有状态都由事件流重建。这个想法在理论层面很优雅但在实际业务里我们只把单独的业务聚合用这种方式做了。比如理赔计算的中间结果因为理赔核定过程有多次审批修改回溯历史很有价值。但保单主数据没有用事件溯源因为查询吞吐量太高每次查询都要聚合事件流性能代价不可控。从实践角度说事件溯源在金融服务里的定位应该是增强现实主数据的审计能力而不是替代现实主数据的存储方案。绝大多数监管审计需要的是事实记录不是状态重建。所以我们采用了折中方案主数据表正常落库事件同时在消息队列和审计存储中各存一份。审计存储的表结构就是写追加不允许更新和删除专门给合规和稽核用。4. 客户主数据治理金融服务项目里最容易被低估的环节很多人觉得客户主数据不就是建一个表存客户信息嘛有什么可说的。但金融服务项目里客户主数据是贯穿所有业务线的核心资产而且质量差起来会让整个系统寸步难行。我们在项目启动后的第二个迭代就专门建了客户中心回头看这个决策是整场重构里性价比最高的一个。4.1 多业务系统的客户数据冲突是怎么出现的老系统里的客户数据来源五花八门线下的业务员录单、官网自助投保、移动端App注册、合作渠道批量导入、电话投保等等。每个来源对客户身份的校验标准都不一样结果同一个自然人被录成三五条记录身份证号不同、手机号不同、姓名写法不同有的还占用了多条保单。这种脏数据直接影响两个重要环节一个是审计和反欺诈客户身份无法统一资金流向就追踪不完整另一个是客户服务同一个客户打电话进来问不同保单的时候客服需要在多个系统里分别查体验极差。我们在新客户中心里引入了客户主索引Customer Master Index的概念通过一组匹配规则身份证号姓名或手机号姓名或证件号出生日期把分散的记录合并成一个客户ID。合并的过程不是简单地去重而是建立从旧ID到新客户ID的映射关系表。所有外围系统通过映射关系查询在过渡期间不需要一次改造完。4.2 个人敏感信息的存取分离方案金融服务的客户数据里身份证号、手机号、银行卡号、住址都属于个人敏感信息这些字段的处理和普通业务字段是两套逻辑。我们在这块踩过好几次坑分享下最终稳定的方案。整个敏感数据处理的链路分成三层存储层、服务层和展示层。存储层里敏感字段一律加密存储用字段级加密而不是整表加密因为整表加密之后就没法走数据库索引了。我们用AES-256-GCM做字段加密密钥由独立的密钥管理服务提供数据库服务本身不掌握明文密钥。服务层对外提供的接口返回的都是脱敏后的数据比如身份证号只留前六后四手机号只留前三后二。如果某个业务场景真的需要明文必须走单独的取明文接口并在审计日志里记录申请原因和操作人。展示层做的另一个重要工作是分类授权。客服系统默认只显示脱敏信息只有经过二次授权才能看到明文且明文展示期间屏幕有水印标记防止截图泄露。这套方案不能说绝对安全但大大降低了核心客户信息在内部流出的面。4.3 客户关系网络的建模比想象中更复杂除了单点客户信息客户与客户之间的关系也是金融服务需要建模的一层。受益人、投保人、被保人、付款人这四类角色在保险业务里经常交叉一个人能既是投保人又是受益人也能给多个保单做付款人。老系统里客户是挂在保单下的这种模型完全无法表达这些关系。新客户中心把参与者Party和合同Contract拆开建模客户关系独立成一张关系图。这个改造让业务方非常满意因为他们经常要查一个客户给几张保单做了付款人这类问题。为了控制复杂度我们只建模了与业务直接相关的关系类型比如投保、受益、代付、授权等没有做社会关系图谱那种泛化的扩展。5. 审计追踪与敏感操作风控这在金融项目里是核心而非附属非金融行业的项目里日志往往是出了问题才去查的东西。但在金融服务项目里审计日志本身就是业务的一部分监管和内审都要看。我们在项目里建了一套独立的审计子系统专门记录所有敏感操作和关键业务事件的完整事实。5.1 审计日志的写入策略和普通日志有什么不一样普通应用日志写的是调试信息追求的是开发效率格式可以随便变过期可以清理。审计日志写的是合规记录有几个鲜明特点不可篡改、不可删除、必须记录操作人、必须记录操作前后状态、保存周期极长。技术上要做到不可篡改一个务实的方案是日志写入后立即做哈希链每条日志的记录里包含上一条日志的哈希值任何人改其中一条都会导致整链校验失败。我们还把每条日志的哈希摘要定期上传到独立的对象存储里做证据留存。这个方案没有用区块链那么重的技术但审计层面已经足够可用。审计日志的覆盖范围要仔细定义不是所有读写都记那样存储成本太高且噪音太大。我们明确了几类必记的操作客户敏感信息的查看和修改、保单核心字段的变更、资金交易各环节的申请与审批、系统授权和权限分配、以及后台管理员的批量操作。每一类都单独定义了审计事件模板字段不同但都包含操作人、操作时间、操作前后快照和IP等环境信息。5.2 审批流与权限控制在金融服务项目里的落地形态金融服务业务里大量操作是需要审批链的比如理赔金额超过某个阈值、客户信息手工修正、保单特殊退保等。我们一开始想用现成的审批流引擎结果发现业务方关于审批节点的规则变化太快通用引擎的配置能力根本跟不上。后来我们自己实现了一个轻量级的审批状态模式把审批通过、驳回、撤回、会签这些动作建模成状态机节点每个节点可以配置所需的审批角色。权限控制上我们做了RBAC和ABAC的分层组合。RBAC解决角色权限的基础分配比如理赔员能操作理赔模块但不能操作收付费模块ABAC解决基于数据属性的条件控制比如某个理赔员只能处理自己名下区域内客户的理赔单子且理赔金额超过一定额度时必须升级审批。ABAC的规则引擎我们用的是策略模式规则配置存储在独立的数据表中产品经理可以通过管理端做配置不需要研发介入。这套权限模型上线之后有一件事让我印象很深业务审计抽查时需要证明某个批次的操作里所有审批决策都是合规的有了状态机和ABAC规则我们能快速导出每个决策对应的规则版本和输入数据做解释性审计。这在老系统里是根本做不到的。6. 工程配套金融服务项目的建设速度和安全常常由基础设施决定很多团队做金融服务项目时容易陷入一个误区把所有精力都放在业务代码和架构上忽视了工程基础设施。但对金融服务这种高价值系统来说测试环境的稳定性、发布的可控性、监控的完备性往往才是决定项目能不能按期上线的主因。6.1 数据库选型和事务边界怎么定金融项目的数据一致性要求高这是所有技术选型的约束条件。我们是基于现有团队的技术栈选择了关系型数据库为主存储因为ACID事务在金融场景里依然是刚需。真正要设计好的是事务的边界而不是把所有跨实体的操作都塞进一个大事务里。举例来说承保之后要同时更新保单状态、生成应收记录、发送事件通知这三件事不能全部做在一个数据库事务里。数据库事务只负责前两件事件发送在事务提交之后进行。如果事务提交成功但事件发送失败系统需要有一个补偿机制来补发事件我们用的是事务发件箱Transactional Outbox模式业务事务里同时写入事件表后台有一个消息派发器扫描事件表并投递到消息队列投递成功后更新事件表状态。这能保证业务数据和事件的最终一致。这个模式的价值在于它不需要分布式事务实现成本很低对现有代码的侵入也小。只要在核心的写操作里多写一条事件记录就行。我们在项目里用这个模式替代了大部分分布式事务方案线上出现的消息丢失极少。6.2 CI/CD和安全扫描在金融服务项目的执行细节金融服务项目的CI/CD和普通项目最大的不同在于每个发布版本都要能追溯到业务需求、代码变更、配置变更和数据库迁移脚本而且整个追溯链条需要在审计系统里留痕。我们在CI流水线里加入了商店般的多重校验单元测试、集成测试、代码扫描、依赖漏洞扫描、容器镜像签名验证。其中依赖漏洞扫描很重要金融项目经常使用开源框架一个高危漏洞在互联网公司也许拖几天没事在金融行业可能直接被安全部门叫停上线。部署上我们坚持数据库变更与代码发布严格分离。数据库变更脚本由专门的DBA审批走的发布通道和代码通道完全不同。代码可以随时回滚但数据库结构变更一旦执行就不能简单回滚。为了控制风险所有数据库变更都是向前兼容的比如先加字段、后改代码再清理旧数据库字段。6.3 可观测性建设从指标到追踪到审计的全栈覆盖金融服务系统的可观测性要覆盖三个层次性能指标、链路追踪和业务监控。性能指标用Prometheus收集各个服务的QPS、延迟、成功率和资源占用链路追踪用OpenTelemetry标准把所有跨服务调用的环节串起来业务监控则是我们投入精力最多的部分因为业务异常往往比性能异常更隐蔽。业务监控的做法是给每个关键业务动作定义业务指标。比如每分钟承保保单数、宽限期保单占比、理赔平均处理时长、收付费不平的金额。这些指标一旦偏离正常区间告警系统就会触发比等客户投诉反馈要快得多。上线第一个月正是这类业务监控帮我们抓到了两个问题一个是夜间批处理任务跑完后部分保单状态没流转另一个是理赔支付和账务对不上。7. 这一趟做下来最想提醒后来者的事项目收尾阶段我们做了几轮复盘有些体感真切的东西想写下来。技术方案选型固然重要但真正决定一个金融服务项目成败的往往是那些容易被忽略的软细节。第一个是领域建模的优先级一定高于技术架构。开发人员容易一上来就兴奋地讨论用哪个框架、消息队列怎么部署但金融服务里最核心的问题是业务规则是否被正确地建模成可扩展的状态和事件。技术是会过时的领域模型的生命周期要长得多。第二个是要尽早建立与业务的统一语言。保险业务里有大量术语比如宽限期、保全、免赔额开发和产品对术语的理解经常有偏差。我们后来把团队内部交流强制统一用业务术语任何代码变量名、数据库字段名都要能直接对应到业务术语表这个动作直接减少了大量无效沟通。第三个是测试数据的构造要足够真实。金融服务项目里非常容易出现测试环境数据太干净导致验证不充分的情况。我们后来搭建了一套脱敏的准生产数据生成工具从老系统里抽取真实保单数据经过脱敏后灌入测试环境发现了一大批在模拟数据下跑不出来的边界逻辑问题。第四个是不要把安全合规当成项目结尾才补的功课。金融服务项目的安全涉及架构阶段的数据分级、设计阶段的权限模型、开发阶段的加密实现、测试阶段的安全测试、上线后的日志审计它是一个贯穿全流程的动作压缩到后期会付出高好几倍的代价。如果这个项目还能有下一步我可能会在数据驱动的领域模型演进上多做尝试比如通过线上业务监控数据来反向校准产品模型的假设。至于保险产品本身的创新玩法那是业务同学的主场但在技术上能不能快速支持新产品的配置和上线架构上的预留已经替我们回答了可以。
返回列表