<?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/architecture/</link><description>Recent content in 架构专栏 on 追风笔记</description><generator>Hugo</generator><language>zh</language><lastBuildDate>Wed, 12 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://f91dba72.wangpeng.pages.dev/architecture/index.xml" rel="self" type="application/rss+xml"/><item><title>聚合根与领域事件：DDD 战术建模</title><link>https://f91dba72.wangpeng.pages.dev/columns/architecture/ddd-aggregates/</link><pubDate>Sun, 15 Mar 2026 00:00:00 +0000</pubDate><guid>https://f91dba72.wangpeng.pages.dev/columns/architecture/ddd-aggregates/</guid><description>&lt;p&gt;银行核心系统里最容易被低估的概念，是「聚合」。很多人把 DDD 战术设计理解成「给实体加几个注解」，结果聚合越写越大，事务越来越长，最后把数据库和团队都拖垮。本文把聚合根和领域事件放回它们本来的位置：一致性边界的设计工具，以及领域向外界发声的窗口。&lt;/p&gt;&#10;&lt;h2 id="为什么需要聚合"&gt;为什么需要聚合&#10;&lt;/h2&gt;&#10;&lt;p&gt;领域模型不是一张 ER 图。ER 图关心「数据怎么存」，聚合关心「哪些数据必须在同一个事务里保持一致」。这两件事经常错位，而错位的代价在金融场景里格外昂贵——账务不平、状态错乱，往往不是因为算法错，而是因为一致性边界划错了。&lt;/p&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;blockquote&gt;&#10;&lt;p&gt;经验法则：如果两条业务规则必须同时为真，它们大概率属于同一个聚合；如果可以接受短暂的不一致，它们就分属不同聚合。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h3 id="聚合不是大对象"&gt;聚合不是「大对象」&#10;&lt;/h3&gt;&#10;&lt;p&gt;最常见的反模式，是把「客户」做成包含地址、联系人、合同、账户、画像的超级对象。它在概念上很完整，但在工程上是个灾难：每次改一个手机号，都要加载半个数据库，还要锁住一堆根本无关的数据。&lt;/p&gt;&#10;&lt;p&gt;聚合的边界应以&lt;strong&gt;事务一致性&lt;/strong&gt;而非&lt;strong&gt;业务概念大小&lt;/strong&gt;来划。概念上属于「同一个东西」的数据，未必需要在同一个事务里。&lt;/p&gt;&#10;&lt;h2 id="聚合根的设计原则"&gt;聚合根的设计原则&#10;&lt;/h2&gt;&#10;&lt;p&gt;聚合根是聚合对外的唯一入口。外部只能持有聚合根的引用，不能直接操作聚合内部的实体或值对象。&lt;/p&gt;&#10;&lt;h3 id="引用靠-id不靠对象"&gt;引用靠 ID，不靠对象&#10;&lt;/h3&gt;&#10;&lt;p&gt;跨聚合的关联，永远用标识符，而不是对象引用。聚合 A 不应直接持有聚合 B 的对象，否则两个聚合会被悄悄绑进同一事务。&lt;/p&gt;&#10;&lt;div class="td-code td-code--untitled" id="td-code-8481552c-fence-0" data-td-code data-td-code-auto-id&#10; data-td-language="java" data-td-line-count="20"&gt;&#10; &lt;div class="td-code__viewport" id="td-code-8481552c-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="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;ownerId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 跨聚合引用：只存 ID&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;Money&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;&#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;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DomainEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pendingEvents&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="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;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;&#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;withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Money&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&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;balance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isLessThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&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;InsufficientBalanceException&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;&#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;balance&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;balance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&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;pendingEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&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;MoneyWithdrawn&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;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;now&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;&#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;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DomainEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;drainEvents&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="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copy&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="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pendingEvents&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;pendingEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&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;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copy&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;h3 id="工厂与仓储"&gt;工厂与仓储&#10;&lt;/h3&gt;&#10;&lt;p&gt;复杂聚合的创建交给工厂，避免调用方掌握内部构造细节；聚合的持久化交给仓储，且&lt;strong&gt;仓储只按聚合根 ID 加载整个聚合&lt;/strong&gt;，不存在「只查一半」的仓储方法。&lt;/p&gt;</description></item><item><title>CQRS 与 Saga：复杂业务的读写分离与最终一致</title><link>https://f91dba72.wangpeng.pages.dev/columns/architecture/cqrs-saga/</link><pubDate>Mon, 20 Apr 2026 00:00:00 +0000</pubDate><guid>https://f91dba72.wangpeng.pages.dev/columns/architecture/cqrs-saga/</guid><description>&lt;p&gt;很多团队把 CQRS 当成「加个 MQ 同步数据」就完事，结果读模型对了、写模型乱了，长事务更无从下手。CQRS 解决「读写诉求不同」，Saga 解决「业务跨多个聚合却不能开大事务」，两者常一起出现但职责不同。&lt;/p&gt;&#10;&lt;h2 id="读写为什么要分家"&gt;读写为什么要分家&#10;&lt;/h2&gt;&#10;&lt;p&gt;写模型关心一致性与不变量，是一组小而强的聚合；读模型关心展示与组装，最好能直接查出前端要的 DTO。诉求冲突时强行共用一个模型，两边都不讨好。&lt;/p&gt;&#10;&lt;div class="td-code td-code--untitled" id="td-code-72f7da03-fence-0" data-td-code data-td-code-auto-id&#10; data-td-language="java" data-td-line-count="3"&gt;&#10; &lt;div class="td-code__viewport" id="td-code-72f7da03-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="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;money&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="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&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="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="n"&gt;findByCardNo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cardNo&lt;/span&gt;&lt;span class="p"&gt;);&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;读侧&lt;/span&gt;&lt;span class="err"&gt;：&lt;/span&gt;&lt;span class="n"&gt;直接投影&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&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;不要为了 CQRS 而 CQRS。单表就能满足读写、流量不大的模块，分家只会制造延迟和负担。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="saga-管理长事务"&gt;Saga 管理长事务&#10;&lt;/h2&gt;&#10;&lt;p&gt;转账、跨境汇款天然跨账户跨系统，不可能用本地事务锁住。Saga 把大事务拆成一串本地事务，每步都有补偿。&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;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&#10;&lt;h2 id="补偿与幂等"&gt;补偿与幂等&#10;&lt;/h2&gt;&#10;&lt;p&gt;Saga 最怕「补偿自己也失败」，所以每步都要幂等：用业务流水号去重，重试不重复扣款。每步记录状态机 &lt;code&gt;PENDING -&amp;gt; DONE / COMPENSATED&lt;/code&gt;，补偿顺序与正向相反，对账做兜底而非第一道防线。&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;最终一致不是「不管了」，而是把一致性从「即时」换成「可验证」：允许短暂中间态，但必须能收敛到正确态。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;落地上，CQRS 让读写各取所需，Saga 让长流程可回退，再加独立对账，复杂业务才算立得住。&lt;/p&gt;</description></item><item><title>六边形架构在银行核心系统的落地</title><link>https://f91dba72.wangpeng.pages.dev/columns/architecture/hex-architecture/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://f91dba72.wangpeng.pages.dev/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>在银行客户信息系统（ECIF）里落地 DDD 聚合</title><link>https://f91dba72.wangpeng.pages.dev/columns/architecture/ddd-aggregate/</link><pubDate>Wed, 12 Aug 2026 00:00:00 +0000</pubDate><guid>https://f91dba72.wangpeng.pages.dev/columns/architecture/ddd-aggregate/</guid><description>&lt;p&gt;ECIF 里「客户」的概念极其庞大：个人、对公、同业，各自的属性、关系、生命周期都不同。如果用一个巨大的 &lt;code&gt;Customer&lt;/code&gt; 实体硬扛，代码会迅速腐化。&lt;/p&gt;&#10;&lt;h2 id="用界限上下文切分"&gt;用界限上下文切分&#10;&lt;/h2&gt;&#10;&lt;p&gt;把客户拆成几个界限上下文：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;客户主数据（Party）&lt;/strong&gt;：统一的自然人/机构标识与基础属性。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;客户画像（Profile）&lt;/strong&gt;：风险偏好、营销标签，读写频率高、变化快。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;客户关系（Relationship）&lt;/strong&gt;：持股、担保、集团关系。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="聚合根怎么定"&gt;聚合根怎么定&#10;&lt;/h2&gt;&#10;&lt;p&gt;每个上下文内部再定聚合根。例如 Party 上下文里，&lt;code&gt;Party&lt;/code&gt; 是聚合根，&lt;code&gt;Address&lt;/code&gt;、&lt;code&gt;Contact&lt;/code&gt; 是其值对象，保证一致性边界内不跨聚合调用。&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;经验：聚合的边界应以「事务一致性」而非「业务概念大小」来划。ECIF 里最容易犯的错，就是把所有客户信息塞进一个聚合。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;这样设计后，主数据服务稳定，画像服务可以独立迭代，互不影响。&lt;/p&gt;</description></item></channel></rss>