<?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://f91dba72.wangpeng.pages.dev/tags/%E9%A2%86%E5%9F%9F%E5%BB%BA%E6%A8%A1/</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://f91dba72.wangpeng.pages.dev/tags/%E9%A2%86%E5%9F%9F%E5%BB%BA%E6%A8%A1/index.xml" rel="self" type="application/rss+xml"/><item><title>银行核心系统建模随笔</title><link>https://f91dba72.wangpeng.pages.dev/blog/banking-core-model/</link><pubDate>Sun, 02 Aug 2026 00:00:00 +0000</pubDate><guid>https://f91dba72.wangpeng.pages.dev/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://f91dba72.wangpeng.pages.dev/blog/reading-notes-ddd/</link><pubDate>Tue, 14 Apr 2026 00:00:00 +0000</pubDate><guid>https://f91dba72.wangpeng.pages.dev/blog/reading-notes-ddd/</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;那一节抄个定义就开始设计。但 Evans 花了整本书篇幅想说的其实是一件事：&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;一个团队如果没有和业务专家持续对话的机制，那么它用不用 DDD 的模式都无所谓——反正模型里没有真正的领域知识。&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; 这是全书性价比最高的概念，也最容易被跳过。如果业务方说&amp;quot;客户&amp;quot;指的是签约主体，而代码里的 &lt;code&gt;Customer&lt;/code&gt; 指的是自然人，那么所有后续设计都建立在一个错位的基础上。我们后来的做法很土：维护一份术语表，中英文对照，明确写出每个词的边界和不包含什么。上线一年下来，它减少的沟通成本比任何架构决策都多。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;第二是模型驱动设计的&amp;quot;绑定&amp;quot;要求。&lt;/strong&gt; 书里强调模型和实现必须绑死——模型改了代码就得改，代码改了模型文档也得改。做不到这一点，模型就退化成挂在墙上的装饰。这也是为什么我不再维护单独的模型图，而是让代码本身成为模型的唯一表达：&lt;/p&gt;&#10;&lt;div class="td-code td-code--untitled" id="td-code-04f985f8-fence-0" data-td-code data-td-code-auto-id&#10; data-td-language="java" data-td-line-count="10"&gt;&#10; &lt;div class="td-code__viewport" id="td-code-04f985f8-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="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="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;RuleProcessor&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;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BigDecimal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BigDecimal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BigDecimal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&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; &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="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="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="c1"&gt;// accrual（计提）不简化成 calc，basis（计息基础）不省略&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;InterestAccrualRule&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;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Money&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;accrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DailyBalance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Rate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DayCountBasis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;basis&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; &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;strong&gt;第三是&amp;quot;深层模型&amp;quot;和重构的关系。&lt;/strong&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;。不再让业务方念文档，而是当场画概念、当场确认术语。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;术语表进代码仓库&lt;/strong&gt;。放在 &lt;code&gt;docs/glossary.md&lt;/code&gt;，跟代码一起评审，改术语要走 PR。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;不再追求完整套用模式&lt;/strong&gt;。战术模式里我们只常用值对象和聚合，事件溯源、CQRS 一律不用，除非有明确的读写比例失衡问题。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;接受模型会被重写&lt;/strong&gt;。第一版模型的目标是能跑并暴露问题，不是一次做对。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;如果只有两小时读这本书，我建议读第一部分和第三部分的第 8 章（突破），跳过所有模式定义。模式的定义随便搜一下都有，而&amp;quot;如何通过对话获得领域知识&amp;quot;这件事，只有认真读原文才能体会到。&lt;/p&gt;</description></item><item><title>DDD 在银行核心系统的落地实践</title><link>https://f91dba72.wangpeng.pages.dev/blog/ddd-banking-core/</link><pubDate>Mon, 12 Jan 2026 00:00:00 +0000</pubDate><guid>https://f91dba72.wangpeng.pages.dev/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>