欧洲盛宝银行如何基于数据网格实现分布式领域驱动架构的最佳实践 - confluent


这篇博文概述了盛宝银行(Saxo Bank)在数字化转型过程中如何通过DDD+数据网格架构实现:快速解决集成并将能数据快速交付给需要它的人。盛宝银行集团(SaxoGroup)成立于1992年,是一家欧洲全牌照银行及领先的金融科技公司。
 
大规模分布式数据管理
数据网格是一种架构范式,自 2019 年由 Zhamak Delgahni 首次提出以来,它逐渐受到关注。盛宝银行如何从数据网格的关键原则开始,概述了如何处理这种架构范式;盛宝如何开始将其变为现实;以及未来的挑战的:

  • 分布式领域驱动架构
    • 数据单体的对立面是分散的联合架构,它迫使您重新考虑处理和所有权的位置。
    • 数据网格不是将数据从域流入中央拥有的数据湖或平台,而是规定数据域应以易于使用的方式托管和服务其域数据集。
  • 自助平台设计
    • 正如《2020 年 DevOps 状况报告》所述,DevOps的高速发展与自助服务功能密切相关,因为它使应用程序团队更高效,控制得到改进,平台团队可以专注于持续的基础设施改进。
    • 在这种情况下,盛宝的自助服务平台超越了纯粹的基础设施,转向专注于使领域团队能够发布自己的数据资产并使用其他团队发布的数据资产。
  • 数据与产品思维融合
    • 在盛宝,我们将数据视为一种产品,并相信产品的可用性可以直接归因于它易于发现、理解和使用。鼓励领域数据团队将产品思维应用于他们提供的数据集,其严谨性与任何其他功能一

 
分布式领域驱动架构

与盛宝相关的数据域与任何其他投资银行或经纪公司没有什么不同。交易等生产者对齐的领域代表业务的交易(事实),当事人等主数据集为此类域提供上下文,而风险等消费者对齐的域往往会消耗大量数据,但产生的数据很少(例如,指标)。


鉴于整个组织的变革步伐,我们知道我们不能依靠中央团队为企业创建和填充规范数据模型。我们的方法必须扩展。相反,我们联合了域数据的所有权及其表示和集中监督。
挑战在于确保整体大于部分之和——域“网格化”并且不存在于真空中。图 2 展示了盛宝的数据运营模式。其目的是“恰到好处”的治理允许:

  • 消费者与生产者解耦(事件超过命令)
  • 有待确定和商定的权威来源
  • 出现并确保信息可以在整个企业中有效使用的标准语言;这种标准或“无处不在”的语言是领域驱动设计 (DDD) 思想的核心,它是消除开发人员和领域专家之间障碍的一种手段
  • 在共同的领域扮演这个重要的角色,我们期待在全国银行使用标准化的基本概念

与我们数据办公室的同事一起,我们还认为这是一个机会,可以确保所有权得到适当的锚定(通常在业务中),并就每个领域的数据问题和战略展开对话。

  • 域团队负责:
    • 确定数据集的权威来源
    • 创建相关数据模型
    • 通过数据结构向其他团队提供数据产品
    • 修复数据质量 (DQ) 问题;问题应该从源头上解决,而不是消费上
  • 企业数据架构师负责:
    • 将我们的数据域整理和塑造成对盛宝具有长期价值的东西
    • 批准对物理域模型的更改
    • 与领域团队合作开发概念模型
  • 数据治理办公室负责:
    • “激活”数据域,包括识别所有权、已知数据质量问题等。

领域语言体现在概念模型(挑战是让它尽可能轻巧)和物理模型(我们用元数据修饰)中。我们不关心从概念图中生成物理模型,因为我们相信这会将重点从对域的推理转移到可视化编程。
这个过程并不容易,我们才刚刚开始。审查步骤肯定会发挥作用,教育和建立实践社区也是如此。名称一致、文档齐全、语义强类型且以小增量更改的模式很快就会被批准。那些不是的将不可避免地需要更长的时间。
此外,该过程还依赖于我们不会第一次就做对的接受,并且需要对领域模型进行持续的管理。事实上,企业数据架构师的角色同样可以被描述为“数据保管人”。学习、迭代和改进。
当然,运营模式是动态的,这只是作为时间点的参考。
 
自助平台设计
数据网格是一种与技术无关的架构范式。在盛宝,我们采用 Confluent 作为我们数据结构的基础层——数据域的权威接口,以及更传统的请求-响应接口。
尽管 Apache Kafka 基于日志的基础很简单,但将其作为企业主干引入也并非没有挑战。我们的目的是让领域团队不必考虑部署连接器的机制、生成语言绑定、如何处理个人身份数据等。这不是微不足道的,特别是考虑到开发平台的广度——主要是 C#,但我们已经在与使用 Python、C++ 和 Kotlin 的团队合作。

我们的自助服务功能严重依赖GitOps,每个数据域都通过两个存储库进行管理:
  • 操作
    • 包含 ACL 的主题配置
    • Kafka连接器的管理
    • 数据质量规则,使用远大期望的DSL 表示
  • 架构
    • Protobuf架构验证和批准
    • 为所有支持的语言生成代码绑定并发布到我们的工件存储库

可发现性不仅与数据的结构有关,还与使整个银行的数据专家能够了解数据产品的沿袭、所有权和相对健康状况(表示为一系列量化指标)有关。
为了解决这个问题,元数据和指标被发布到数据工作台——我们对 LinkedIn 开源项目DataHub 的实施——让整个银行的数据专家能够了解发布到 Apache Kafka 的数据资产及其关系。这里有很多改进的余地,例如,允许元数据管理在工具本身中进行,而不是直接在模式文件中进行。
 
数据与产品思维融合
(数据)产品的可用性归结为它可以被发现、理解和使用的难易程度。集中式架构的一个优点是在不同的数据域之间保持一致的用户体验要容易得多,并确保用户能够将他们的心智模型从一个域转移到另一个域。我们的联合架构需要一种完全不同的方法。
考虑到这一点,我们的自助服务平台对如何在盛宝中使用 Kafka 采取了一些固执的观点。标准管道确保在所有领域采用通用方法——样式检查、代码绑定的生成、数据质量规则执行/报告以及元数据如何推送到数据工作台等。
我们努力使数据资产具有自描述性,并力求明确表示概念。我们的信念是,通过在域内和跨域一致和有效地使用元数据,我们可以提高数据的可用性和新数据产品的上市时间。从根本上说,我们不希望生产者数据的形状发生显着变化,至少在最初的情况下是这样。小的改变可以带来大的不同:
  • 跨领域采用一致的风格指南
  • 非常强调强类型(“货币”带有商业含义,“字符串”没有)
  • 尽可能符合行业标准
  • 记录一切
  • 禁止“魔法值”
  • 在源头捕获信息分类
  • 链接相关概念

然后通过数据工作台显示这些信息,它在我们的实施中发挥着关键作用——不仅用于发现数据资产,而且相信在意义、所有权和质量方面创建每个数据域和资产的可见性将有助于推动持续改进。
 

那么,我们如何鼓励每个数据域中的数据具有一致的外观和感觉?以下部分涉及我们鼓励团队在设计数据契约时考虑的最佳实践,从消息格式本身开始。
1. 选择消息格式
有很多文章讨论了结构化数据的不同序列化机制的优点,例如Avro 中的 Schema Evolution、Protocol Buffers 和Martin Kleppmann 的Thrift。通常,这些与模式管理和编码效率有关。
一个经常被忽视的考虑因素是语义注释(又名元数据)可以轻松嵌入到模式中。尽管 XSD(XML 的模式定义语言)名声不佳,但事实证明FpML等标准在全球银行中非常成功,因为与其他情况相比,它们允许更严格的消息定义水平。事实上,鉴于 FpML 的开放性,许多人已将其用作他们自己无处不在的语言的基础。
然而,随着 XML 不再是必需品,我们开始寻找替代品——特别是 Confluent 当前支持的那些:Avro、JSON 和 Protocol Buffers (Protobuf)。
在考虑使用 JSON 编码的可行性时,FpML 架构师工作组指出,根本不可能在 JSON 中表达相同的不同数据类型和语言约束集。除此之外,表达十进制值的唯一可靠方法是将其编码为字符串。此外,JSON Schema 没有表达自定义语义注释的方法。由于这些原因,我们没有考虑 JSON Schema。
在这种情况下,Avro 的表现稍好一些,尤其是与 Avro 接口定义语言 (IDL) 结合使用时,它允许架构可组合性。我们可以将语义注释表示为松散类型的名称-值对,以向类型和字段添加额外的属性。尽管 Avro 定义了一小组原语,但该语言已扩展为包括许多核心逻辑类型(十进制、UUID、日期和时间)。
Protobuf 更进一步,允许使用“自定义选项”进行强消息类型和字段级注释。这使得编译时检查成为可能,这当然是有利的。
另一个考虑因素是语言绑定的成熟度。虽然盛宝Saxo 最初选择了 Avro,但我们不情愿地意识到这是我们推出过程中的一个主要摩擦点。C# 和 Python 实现落后于 JVM,尽管我们确实提供了一些修复(感谢 Matt Howlett 的支持),但我们认为围绕 C# 实现的支持会让人分心。
Protobuf 相对于 Avro 的另一个优点是,您的绑定将遵循目标语言的类型和属性的风格规范,而不管模式中使用的命名约定如何。尽管这似乎是一个微不足道的问题,但如果没有正确考虑,它就是另一个摩擦源。
我们最终在 2020 年底切换到 Protobuf,不久之后它就被 Confluent Schema Registry 支持为一等公民。当然,对于感兴趣的语言绑定——C#、Python、C/C++,以及随着 Kafka Streams 越来越受到关注,JVM——我们发现这些实现比 Avro 的情况更加一致。
 
2. 自描述模式
对于那些不熟悉 Protobuf 的人,请放心,如果您已经掌握了XSD的复杂性,您会发现它就像在公园里散步一样。
简而言之,无论是事件还是事件引用的类型,复杂类型都表示为“消息”。虽然语法略有不同,但“选项”(即语义注释)可以在消息(类型)或字段(属性)级别表示。有关更多详细信息,请参阅Proto3 语言指南
 
3. 命名
命名很难,但使用Uber 的样式指南可以鼓励一致性并提供经过深思熟虑的版本控制方法。
我们还有许多您希望在任何编码标准中看到的准则:奇异值应该有一个单数名称,重复字段应该有一个复数名称,等等。
 
4. 身份标识
企业标识符的一致使用是使这种分布式模型工作的关键要求之一。毕竟,考虑一下网格这个词背后的含义:“在正确的位置连接在一起”。标识符唯一地标识一个实体,可以被认为是域的“主键”,这是我们数据网格实现背后的基本原则之一。
标识符使用 Protobuf 的 IDL 定义如下:

// This is how we pass 'Thing' by reference
message ThingIdentifier {
    int64 id = 1;
}

按照惯例,我们将标识符的唯一属性命名id为数字标识符和code字母数字标识符。跨域普遍使用的企业标识符必须以通用方式定义。
 
5. 引用
引用可以被认为是具有较弱约束的替代标识符。例如,PaymentReference可能是客户提供的自由格式文本字段。下面是一个例子:

// User supplied reference. Not necessarily unique
message ThingReference {
    string ref = 1;
}

按照惯例,我们将引用的唯一属性命名为ref(通常为 type string)。企业引用必须以通用方式定义。
 
6. 枚举和schemes
许多数据元素被限制为仅包含一组有限的可能值中的一个。这种受限制的值集通常称为枚举。
Protobuf 与许多其他语言类似,支持使用枚举类型。如果值的范围很小(例如 < 10)并且预计不会经常变化,那么使用enum. 然而,通常预计类型将引用外部编码方案,一个取自 FpML的概念。
schemes在 IDL 中引用如下:
// 根据 ISO 3166 由三个字符的字母代码表示的货币
// https://www.iso.org/iso-4217-currency-codes.html
// https://spec.edmcouncil.org/fibo/ontology/FND/Accounting/CurrencyAmount/Currency
message CurrencyIdentifier {
    option (metadata.coding_scheme) = "topic://reference-currency-compact-v1";
    // Alphabetic (three letter) code
    string code = 1;
}

在这个例子中,消息的阅读器被引导到压缩主题reference-currency-compact-v1,以获得货币代码及其含义的明确列表。
schemes不仅可以帮助接收者了解值的范围,而且还为自动化数据质量监控打开了大门。理想情况下,schemes引用另一个主题,但如果团队引用文档,我们(几乎)同样高兴。
 
7. 信息分类
盛宝定义了四种信息分类,每一种都意味着特定的敏感度。这反映在下面所示的架构中:
// Name of a natural person
message Name {
    option (metadata.msg_info_class) = INFO_CLASS_PERSONAL;
   // 一个人的名字或名字,即为他们选择的名字
      // 出生时或随后由他们根据出生时的名字更改的名字
    string given_name = 1;
}

虽然这预计会反映在消息级别,但在字段级别表达这一点可能有正当理由。在这种情况下,field_info_class应指定该选项。如果未说明,则假定为默认的“仅限内部”分类。
我们进入云的旅程的一个关键部分是确保 PII 数据是加密的。我们的目的是最终直接从模式注释中驱动这一点,使开发团队免于关注这些细节。
 
8.外部模式
尽管我们已经对 Protobuf 进行了标准化,但对于某些用例,我们支持“自带架构”的想法。在这种情况下,必须使用以下external_schema选项显式引用所使用的实际模式:

// An example of an external schema
message EventWithExternalSchema {
    XmlString vendor_string = 1 [(metadata.external_schema) = "https://example/third-party.xsd"];
}


请注意,虽然由vendor_string表示的有效负荷内容中可能包含对third-party.xsd的引用,但它必须在元数据中明确引用,以便在“设计时”易于使用。
 
9.弃用
弃用是进化的一个组成部分,它让消费者深入了解您未来的重大变革计划。弃用可以在字段或消息级别表示,如下所示:

// An example of a deprecated attribute
message EventWithDeprecatedField {
    // Seemed like a good idea at the time. Will be removed (or reserved) at a later date
    int32 old_field = 1 [deprecated = true];
    // Much better idea
    int64 new_field = 2;
}
 
[b]10.[/b]
外部标准
您应该如何表示电子邮件地址?一个约会?一个产品?监管提交?很可能有一个标准,所以让我们使用它。
只要可行,我们总是在我们的文档中引用这些标准——或者作为唯一的定义,或者与盛宝的实施相关。我们使用下图所示的term_source和选项通过“业务术语business term”选项将域模型链接到外部标准term_ref:

// 该度量是以货币单位指定的金额(带有
// 货币单位的数字)。在推断(即未填充)货币的地方,对此类型的引用
// 应明确说明价格以哪种货币计价。
message MonetaryAmount {
    option (metadata.term_source) = TERM_SOURCE_FIBO;
    option (metadata.term_source_ref) = "https://spec.edmcouncil.org/fibo/ontology/FND/Accounting/CurrencyAmount/MonetaryAmount";
    // 财务金额
    DecimalValue amount = 1;
    // 可选的货币代码
    CurrencyIdentifier ccy = 2;
}

该示例明确指出, MonetaryAmount的定义直接取自金融行业业务本体的同名术语。
 
11. 链接词
当我们迭代域模型时,我们可能会无意中在不同的数据域中复制概念。无论进行多少前期设计,这都是不可避免的。与其等待重大更改,还需要一种机制,使我们能够在不影响现有生产者或消费者的情况下引用现有概念。
我们通过field_term_link选项将领域模型中的元素链接到公认的业务术语的权威定义:
// 链接到“业务术语”
message EventWithLinkedTerm {
    // 交易货币
    string trade_currency = 1 [(metadata.field_term_link) = "CurrencyIdentifer"];
}

在这个例子中,我们trade_currency在不影响模式兼容性的情况下进行了注释以引用无处不在的语言“货币currency”的表示。这不仅可以用来改进文档,还可以作为领域模型未来迭代的备忘录。
 
12. 测量值
这种设计的价值在于我们能够进一步利用数据。我们怎么知道我们走在正确的道路上?在我们的案例中,盛宝已经确定了一些我们将用来衡量实现我们愿景的进展的指标:
  • 网格中的连接数(断开连接的域不太可能提供更广泛的价值)
  • 生产者:消费者比率
  • 创建面向消费者的数据产品的准备时间
  • 数据产品度量(例如数据质量覆盖的趋势)
  • 测试覆盖率(应变能力)
  • 等等

 
未来方向
尽管盛宝已经对这些想法进行了一段时间的迭代,但直到最近,随着我们寻求解决扩展挑战,采用率才有所增长。我们的主要重点是:
  • 继续降低平台准入门槛
  • 通过例如自动对帐框架增加平台的附加值
  • 与我们在数据办公室和整个银行的同事合作,嵌入领域思维
  • 继续与AcrylLinkedIn DataHub社区合作,通过以下方式使 Data Workbench 成为所有数据专业人士的一站式商店:
    • 以领域为中心的用户界面
    • 域健康的游戏化
    • 模式文档的众包改进
    • 在用户界面中显示数据质量规则和结果
    • 包括所有其他上游/下游平台
  • 使团队能够轻松采用 ksqlDB 等工具

更多数字化转型
更多数据网格