<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>银行核心 on 追风笔记</title><link>https://88ok.github.io/tags/%E9%93%B6%E8%A1%8C%E6%A0%B8%E5%BF%83/</link><description>Recent content in 银行核心 on 追风笔记</description><generator>Hugo</generator><language>zh</language><lastBuildDate>Sun, 02 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://88ok.github.io/tags/%E9%93%B6%E8%A1%8C%E6%A0%B8%E5%BF%83/index.xml" rel="self" type="application/rss+xml"/><item><title>六边形架构在银行核心系统的落地</title><link>https://88ok.github.io/columns/architecture/hex-architecture/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://88ok.github.io/columns/architecture/hex-architecture/</guid><description>&lt;p&gt;银行核心最痛的不是业务复杂，而是&lt;strong&gt;业务逻辑和周边技术死死绑在一起&lt;/strong&gt;：换连接池要改核心，接新渠道要动账务，单测要起一整套中间件。六边形架构（Ports &amp;amp; Adapters）给出干净解法。&lt;/p&gt;&#10;&lt;h2 id="核心端口与适配器"&gt;核心、端口与适配器&#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;领域核心&lt;/strong&gt;：纯业务规则，不依赖任何框架、数据库或 HTTP 客户端。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;端口&lt;/strong&gt;：核心暴露（入站）和需要（出站）的接口，用 Java 接口表达意图。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;适配器&lt;/strong&gt;：把端口接到具体技术——REST 控制器是入站适配器，JDBC 仓储是出站适配器。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="td-code td-code--untitled" id="td-code-89492c2e-fence-0" data-td-code data-td-code-auto-id&#10; data-td-language="java" data-td-line-count="5"&gt;&#10; &lt;div class="td-code__viewport" id="td-code-89492c2e-fence-0-viewport" data-td-code-viewport&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;AccountRepository&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 端口：只定义要什么&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AccountId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JdbcAccountRepository&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AccountRepository&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* 技术留在外围 */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;判断架构是否干净的一条硬标准：删掉数据库、MQ、Web 框架，核心业务代码还能原封不动地跑并被测。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="在银行核心里怎么切"&gt;在银行核心里怎么切&#10;&lt;/h2&gt;&#10;&lt;p&gt;核心账务、计息、限额放在中心；大小额网关、CBS 接口、短信、风控回调全做成外围适配器。监管规则变了只动核心，接新清算通道只加一个适配器。&lt;/p&gt;&#10;&lt;div class="td-table-scroll td-table-scroll--static"&gt;&#10;&lt;table&gt;&#10; &lt;thead&gt;&#10; &lt;tr&gt;&#10; &lt;th scope="col"&gt;层次&lt;/th&gt;&#10; &lt;th scope="col"&gt;内容&lt;/th&gt;&#10; &lt;th scope="col"&gt;依赖框架&lt;/th&gt;&#10; &lt;/tr&gt;&#10; &lt;/thead&gt;&#10; &lt;tbody&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;领域核心&lt;/td&gt;&#10; &lt;td&gt;账务、计息、限额&lt;/td&gt;&#10; &lt;td&gt;否&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;应用层&lt;/td&gt;&#10; &lt;td&gt;用例编排&lt;/td&gt;&#10; &lt;td&gt;否&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;适配层&lt;/td&gt;&#10; &lt;td&gt;REST/JDBC/MQ&lt;/td&gt;&#10; &lt;td&gt;是&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&#10;&lt;h2 id="收益与代价"&gt;收益与代价&#10;&lt;/h2&gt;&#10;&lt;p&gt;收益是可单测、可独立部署、渠道可替换；代价是初期抽象成本与团队纪律。在银行这种长生命周期系统里，核心被技术债锁死的代价远高于前期投入。&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;架构不是墙上的图，而是写在依赖箭头里的纪律。六边形的价值，是让「技术变了业务不动」成为默认结果。&lt;/p&gt;&#10;&lt;/blockquote&gt;</description></item><item><title>银行核心系统建模随笔</title><link>https://88ok.github.io/blog/banking-core-model/</link><pubDate>Sun, 02 Aug 2026 00:00:00 +0000</pubDate><guid>https://88ok.github.io/blog/banking-core-model/</guid><description>&lt;p&gt;核心系统的模型看起来很朴素：账户、余额、交易、分录。真正做进去才发现，每一个概念背后都有一堆不能妥协的约束，而这些约束在需求文档里往往一个字都没写。这篇是零散的建模笔记，按我认为重要性排的顺序。&lt;/p&gt;&#10;&lt;h2 id="核心系统到底在建模什么"&gt;核心系统到底在建模什么&#10;&lt;/h2&gt;&#10;&lt;p&gt;先把定位说清楚。核心系统建模的对象不是&amp;quot;业务流程&amp;quot;，而是&lt;strong&gt;会计事实&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;客户在手机上点一下转账，这是渠道行为；反洗钱要不要拦、限额够不够，这是风控与协议判断；而核心真正负责的只有一件事：&lt;strong&gt;在正确的会计日，把一组借贷平衡的分录准确地记下来，并保证余额永远等于分录累计的结果&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;想清楚这一点，很多设计争论会自动消失。比如&amp;quot;转账的营销活动规则要不要放核心&amp;quot;——不放，那不是会计事实。核心的表越少越好，能力越单一越好。&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;判断一个需求该不该进核心，只问一句：它会改变账务事实吗？不会，就放外围。核心系统膨胀的过程，就是这条边界一次次让步的过程。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="账户模型一棵不能长歪的树"&gt;账户模型：一棵不能长歪的树&#10;&lt;/h2&gt;&#10;&lt;h3 id="账户号不是主键"&gt;账户号不是主键&#10;&lt;/h3&gt;&#10;&lt;p&gt;这是我见过最多的建模错误。账号是&lt;strong&gt;业务标识&lt;/strong&gt;，有格式规则、会变更、会重开、可能因迁移换号段。用它做物理主键，等到某天要做账号升位或者跨行并库，整个系统的外键关系会一起爆炸。&lt;/p&gt;&#10;&lt;p&gt;正确做法是内部 ID 与业务账号分离：&lt;/p&gt;&#10;&lt;div class="td-code td-code--untitled" id="td-code-b9acdbec-fence-0" data-td-code data-td-code-auto-id&#10; data-td-language="java" data-td-line-count="18"&gt;&#10; &lt;div class="td-code__viewport" id="td-code-b9acdbec-fence-0-viewport" data-td-code-viewport&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AccountId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 内部标识，永不变更，不对外暴露&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AccountNo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;accountNo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 业务账号，可变更，对外展示&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ProductCode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Currency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AccountStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LocalDate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;openDate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/** 账号变更保留历史，因为凭证和流水引用的是历史账号 */&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;changeAccountNo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AccountNo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newNo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AccountStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ACTIVE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DomainException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;非正常状态账户不允许换号&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;registerEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AccountNoChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;accountNo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newNo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accountNo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newNo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;&lt;code&gt;AccountNoChanged&lt;/code&gt; 事件必须落库。半年后有人拿旧账号来查流水，你要能翻译过去。&lt;/p&gt;</description></item><item><title>单元化架构：银行核心的容灾与扩展底座</title><link>https://88ok.github.io/blog/unitization-deploy/</link><pubDate>Mon, 26 Jan 2026 00:00:00 +0000</pubDate><guid>https://88ok.github.io/blog/unitization-deploy/</guid><description>&lt;p&gt;单元化被讲成&amp;quot;异地多活的银弹&amp;quot;，但它真正解决的问题只有两个：把故障爆炸半径收进一个单元，以及让容量可以按单元横向叠加。理解不了这两点，做出来的往往只是一套更复杂的分库分表。&lt;/p&gt;&#10;&lt;h2 id="为什么核心要做单元化"&gt;为什么核心要做单元化&#10;&lt;/h2&gt;&#10;&lt;p&gt;传统主备架构的困境是：备机平时不承载流量，切换时没人敢按那个按钮。因为没人验证过备机在真实压力下能不能扛住。&lt;/p&gt;&#10;&lt;p&gt;单元化把这件事变成常态：每个单元都在真实处理流量，每个单元都是完整的业务闭环——接入层、应用层、数据库全套齐备。某个单元挂了，把它的流量按客户维度切到其他单元，其他单元本来就在跑同样的代码和同样的负载模型。&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;容灾能力不是靠架构图证明的，是靠每月切一次流量证明的。单元化的最大价值是让切换变成一件平常事。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="单元怎么切"&gt;单元怎么切&#10;&lt;/h2&gt;&#10;&lt;p&gt;切分维度只有一个原则：&lt;strong&gt;保证绝大多数交易在单元内闭环&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;对零售银行来说，客户号是最合适的切分键。同一个客户的账户、协议、流水都在一个单元，查余额、转账（同行同单元）这些高频操作不跨单元。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;按客户号切&lt;/strong&gt;：适合零售，单元内闭环率通常能到 90% 以上&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;按机构切&lt;/strong&gt;：适合对公，但容易出现大行倾斜&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;按账号切&lt;/strong&gt;：看着均匀，实际会把同一客户的账户打散，跨单元查询暴增&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;要留一类特殊单元：全局单元。存放客户号生成、产品参数、机构信息这些必须全局唯一或全局一致的数据。全局单元只读为主，写入频率极低，靠单向复制分发到各业务单元。&lt;/p&gt;&#10;&lt;h2 id="路由与数据一致性"&gt;路由与数据一致性&#10;&lt;/h2&gt;&#10;&lt;p&gt;路由要在尽可能靠前的位置完成。我们的做法是接入层解析出客户号后直接算单元，错误路由的请求由应用层做二次校验并拒绝，绝不允许&amp;quot;就近处理&amp;quot;。&lt;/p&gt;&#10;&lt;div class="td-code td-code--untitled" id="td-code-e106a432-fence-0" data-td-code data-td-code-auto-id&#10; data-td-language="yaml" data-td-line-count="11"&gt;&#10; &lt;div class="td-code__viewport" id="td-code-e106a432-fence-0-viewport" data-td-code-viewport&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;shard_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;customer_no&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;algorithm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mod &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 客户号后四位取模，共 64 个逻辑单元&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;logical_units&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;unit-sh-01&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0-15&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;unit-sh-02&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;16-31&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;unit-sz-01&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;32-47&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;unit-sz-02&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;48-63&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;global_unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unit-global&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strict_check&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 应用层校验不通过直接拒绝，不做转发&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;逻辑单元数要一次性定足（我们定了 64），物理单元可以少。扩容时把逻辑单元区间搬到新物理单元，不用改路由算法，也不用重新哈希全量数据。&lt;/p&gt;&#10;&lt;p&gt;跨单元的交易（比如两个客户不在同一单元的转账）不要试图做跨单元强一致事务。正确做法是拆成两段本地事务加异步通知，中间状态用&amp;quot;在途&amp;quot;余额表达，配日终对账兜底。&lt;/p&gt;&#10;&lt;h2 id="踩过的坑"&gt;踩过的坑&#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;路由键泄漏到业务逻辑里&lt;/strong&gt;。有个模块直接按账号查库，没带客户号，单元化后查不到数据。所有 DAO 必须强制要求分片键，缺失就报错，不要默认广播查询。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;广播查询滥用&lt;/strong&gt;。运营后台要查&amp;quot;全行某状态的账户&amp;quot;，图省事写成遍历所有单元。单元多了之后这类查询直接拖垮全部单元。这类需求应该走离线数仓。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;忘了单元内的容灾&lt;/strong&gt;。单元化解决的是单元级故障，单元内部的数据库高可用还得单独做，两者不能互相替代。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;单元化的复杂度是实打实的，它换来的是可验证的容灾能力和线性的扩容路径。如果业务量还撑不起这份复杂度，主备加读写分离活得更舒服。&lt;/p&gt;</description></item><item><title>DDD 在银行核心系统的落地实践</title><link>https://88ok.github.io/blog/ddd-banking-core/</link><pubDate>Mon, 12 Jan 2026 00:00:00 +0000</pubDate><guid>https://88ok.github.io/blog/ddd-banking-core/</guid><description>&lt;p&gt;银行核心系统是一类很特殊的软件：它的业务规则几十年没怎么变，但承载它的代码每隔七八年就要重写一次。我参与过一次核心的部分重构，也旁观过一次彻底失败的重写。DDD 在这两次里都被提过，但只有一次真的起了作用。这篇把我认为有效的部分和纯属自我感动的部分分开写。&lt;/p&gt;&#10;&lt;h2 id="为什么银行核心需要-ddd"&gt;为什么银行核心需要 DDD&#10;&lt;/h2&gt;&#10;&lt;p&gt;老核心的问题从来不是技术栈老。COBOL 写的联机交易照样能扛住每秒几千笔。真正的问题是：业务知识只存在于少数几个人的脑子里，代码里找不到对应的表达。&lt;/p&gt;&#10;&lt;p&gt;一段典型的老代码长这样：把交易码、账户类型、产品编号、机构号混在一个几百行的过程里，用几十个 &lt;code&gt;IF&lt;/code&gt; 分支区分场景。你想知道&amp;quot;活期账户计息到底怎么算&amp;quot;，只能从头读到尾，然后祈祷没漏掉某个补丁分支。&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;代码可以重写，业务知识不能。重构核心系统的第一目标不是换语言，而是把散落在分支里的规则重新变成可以被讨论的概念。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;这正是 DDD 唯一真正值得投入的地方：它提供了一套让业务专家和工程师用同一套词汇讨论问题的方法。别的都是附加品。&lt;/p&gt;&#10;&lt;h2 id="战略设计先切限界上下文"&gt;战略设计：先切限界上下文&#10;&lt;/h2&gt;&#10;&lt;p&gt;如果只能做 DDD 的一件事，就做上下文切分。这一步做错，后面所有战术模式都是白费。&lt;/p&gt;&#10;&lt;h3 id="以业务能力而非表结构切分"&gt;以业务能力而非表结构切分&#10;&lt;/h3&gt;&#10;&lt;p&gt;最常见的错误是按数据库表切：账户表归账户服务，交易表归交易服务。结果是每笔转账都要跨三个服务改数据，分布式事务满天飞。&lt;/p&gt;&#10;&lt;p&gt;我们最后落在这几个上下文上：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;客户与关系&lt;/strong&gt;：客户主体、关系人、证件、KYC 状态&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;账户与协议&lt;/strong&gt;：账号、账户属性、签约关系、限额协议&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;交易处理&lt;/strong&gt;：交易受理、要素校验、路由、冲正&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;账务核算&lt;/strong&gt;：分录、余额、总账、日终结转&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;产品参数&lt;/strong&gt;：利率、费率、期限、计息规则&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;关键判据是：&lt;strong&gt;一次业务动作的强一致性要求是否落在同一个上下文内&lt;/strong&gt;。转账的&amp;quot;扣款成功且分录平衡&amp;quot;必须强一致，所以账务核算不能再往下拆；而&amp;quot;转账成功后更新客户活跃度&amp;quot;完全可以最终一致，那就是两个上下文。&lt;/p&gt;&#10;&lt;h3 id="上下文映射的三种常见关系"&gt;上下文映射的三种常见关系&#10;&lt;/h3&gt;&#10;&lt;div class="td-table-scroll td-table-scroll--static"&gt;&#10;&lt;table&gt;&#10; &lt;thead&gt;&#10; &lt;tr&gt;&#10; &lt;th scope="col"&gt;关系类型&lt;/th&gt;&#10; &lt;th scope="col"&gt;银行场景举例&lt;/th&gt;&#10; &lt;th scope="col"&gt;集成方式&lt;/th&gt;&#10; &lt;th scope="col"&gt;注意点&lt;/th&gt;&#10; &lt;/tr&gt;&#10; &lt;/thead&gt;&#10; &lt;tbody&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;共享内核&lt;/td&gt;&#10; &lt;td&gt;账户上下文与账务上下文共用币种、金额模型&lt;/td&gt;&#10; &lt;td&gt;公共 jar，严格版本管理&lt;/td&gt;&#10; &lt;td&gt;只放值对象，禁止放业务逻辑&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;客户方-供应方&lt;/td&gt;&#10; &lt;td&gt;交易处理调用账务记账&lt;/td&gt;&#10; &lt;td&gt;同步 RPC，供应方定契约&lt;/td&gt;&#10; &lt;td&gt;契约变更必须双版本并行&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;防腐层&lt;/td&gt;&#10; &lt;td&gt;新模块访问老核心账户查询&lt;/td&gt;&#10; &lt;td&gt;适配器 + 模型转换&lt;/td&gt;&#10; &lt;td&gt;老模型绝不允许穿透进来&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&#10;&lt;h3 id="一个反例"&gt;一个反例&#10;&lt;/h3&gt;&#10;&lt;p&gt;我们曾经把&amp;quot;限额&amp;quot;单独切成一个上下文。听起来很干净，实际上限额校验必须和交易受理在同一个事务里完成，跨服务调用一下就把联机响应时间从 40ms 拖到 90ms，还引入了新的失败分支。半年后合并回交易处理上下文。&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;上下文边界的正确性，用一致性要求和调用频次验证，不用概念优雅度验证。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="战术落地聚合实体与值对象"&gt;战术落地：聚合、实体与值对象&#10;&lt;/h2&gt;&#10;&lt;p&gt;战术模式在核心系统里的价值远低于战略设计，但用对了确实能减少一批低级 bug。&lt;/p&gt;&#10;&lt;h3 id="聚合的粒度取决于一致性边界"&gt;聚合的粒度取决于一致性边界&#10;&lt;/h3&gt;&#10;&lt;p&gt;账务这块我们的聚合是&amp;quot;分录凭证&amp;quot;（Voucher），不是&amp;quot;账户&amp;quot;。一张凭证包含多条分录，借贷必须平衡，这是不可分割的一致性单元。账户余额是凭证记账的结果，通过事件更新。&lt;/p&gt;</description></item></channel></rss>