不可变账本:通过 TypeScript 与 Design Patterns 实现数据完整性
Source: Dev.to
摘要
在稳健的金融架构中,系统的完整性并不依赖于数据库行的当前状态(例如 UPDATE accounts SET balance = 50),而是依赖于其整个历史的推导。
本文探讨 Ledger Pattern 作为可靠系统的基础原语。我们审视一个简化的 TypeScript 实现,演示 事件溯源(Event Sourcing)、不可变性(Immutability) 与用于测试的 建造者模式(Builder Pattern)——这些概念是区块链技术的架构祖先。
- 每个客户一个账本:线性、仅追加的历史。
- 零变更:我们从不编辑交易,只是添加新行。
- 派生状态:当前余额仅是历史记录的求和。
如果你存储余额,就必须信任数据库的更新。如果你存储账本,就可以证明计算过程的正确性。
问题:金融领域的可变状态
在标准的 CRUD 应用中,更新余额会销毁先前的状态。如果数据库写入失败或恶意行为者篡改了数值,历史就会丢失。这通常被称为 “破坏性更新”问题。
相对而言,账本是 仅追加的事件日志。当前余额 从不被直接存储;它通过对所有交易历史的归约(reduce)确定性地计算得到。
状态派生与不可变性
getBalance()不是读取已存储的变量;它通过归约交易历史来计算状态。- 历史数组仅以
ReadonlyArray或展开复制的形式暴露,确保虽然可以计算状态,但历史本身保持防篡改——这是审计可追溯性的关键要求。
区块链前提
为了安全,区块链在账本概念上进一步加入了密码学哈希(例如 Merkle 树)来链接条目。在分布式账本中,“历史”变成了一系列区块,且不可变性由密码学而非内存作用域保证。
关键点
- 私有历史 与 只读返回类型 确保一旦记录条目,就无法被外部消费者篡改。
- 密码学链接(哈希)在分布式节点之间提供防篡改证据。
使用建造者模式进行测试
为了在不编写重复样板代码的情况下验证此逻辑,我们采用 建造者模式。这使我们能够使用流畅、富表达的接口构造复杂的交易场景,从而提升可读性和可维护性。
测试套件确认了两个关键行为:
正确的派生
余额是对整个交易历史进行归约得到的。
不可变性
尝试修改暴露的历史数组(常见攻击向量)不会影响内部状态。
注意: 该实现是为教学目的设计的结构性原语。生产级金融系统需要额外的稳健层:
- 并发控制: 处理多笔交易同时发生时的竞争条件(例如使用乐观并发控制)。
- 持久化: 将事件日志存储在持久化数据库中(如 PostgreSQL 或分布式日志 Kafka)。
- 业务逻辑校验: 强制执行如防止透支等规则。
- 智能合约演进: 添加与区块链环境中智能合约相匹配的校验逻辑。
结论
虽然简单,这一模式展示了存储数据与存储事实之间的区别。无论你是在构建银行系统还是分布式账本,原则都是相同的:状态是历史的函数。
当你扩展此模式时,“账本”会从一个简单的类演变为分布式日志,最终成为现代区块链网络中去中心化共识机制的一部分。