写点什么

基于 Data Mesh 构建分布式领域驱动架构的最佳实践

GRAHAM STIRLING

2021 年 7 月 20 日

基于Data Mesh构建分布式领域驱动架构的最佳实践

虽然遵守 GDPR 和 BCBS 239 等法规可能是一项挑战,但它们只不过是现代数据平台的最佳实践指南。一个有远见的组织应该有一个数据基础结构(data fabric ),以解决常见的非功能需求,同时还要有一个运营模式,以识别数据的战略价值。本文介绍 Saxo 银行如何借助数据网格架构来实现这一愿景。在数据平台团队的推动下,我们彻底地重新思考了组织内的数据使用情况。


本文最初发布于 Confluent 官方博客,经原作者授权由 InfoQ 中文站翻译并分享。


(图片转载自Superflex


所有数据归人民”,这在企业里是一个非常有吸引力的主张。然而,对于许多组织来说,快速解决集成问题,将数据提供给需要的人来解锁洞察和创新还是一个遥远的梦。

 

虽然遵守 GDPR 和 BCBS 239 等法规可能是一项挑战,但它们只不过是现代数据平台的最佳实践指南。一个有远见的组织应该有一个数据基础结构(data fabric ),以解决常见的非功能需求,同时还要有一个运营模式,以识别数据的战略价值。

 

本文介绍Saxo银行如何借助数据网格架构来实现这一愿景。在数据平台团队的推动下,我们彻底地重新思考了组织内的数据使用情况。

大规模分布式数据管理

虽然在过去二十年中,科技行业在大规模数据处理方面已经取得了长足的进步,但在很大程度上,仍然未能将数据管理扩展到组织层面。这既是工作方式的问题,也是技术变革的问题。简而言之,从现在开始,我们要把数据当作一种产品来考虑,并考虑它是否容易发布、发现、理解和消费。

 

自 2019 年 Zhamak Delgahni 首次提出这一概念以来,作为一种架构范式,数据网格逐步获得了人们的关注。2018 年底,Saxo 银行开始实现一种新的数据架构,走上了类似的道路,我们很快就意识到,我们思路与数据网格非常相近。

 

围绕 Delgahni 最初的设想,相关的文献资料已经很多,本文不会再做这方面的讨论。取而代之,我们将大概介绍下 Saxo 公司如何从数据网格的关键原则入手探索这一架构范式,如何将其变成现实,以及还面临什么挑战。


  • 分布式领域驱动架构

  • 去中心化的联合架构是与数据单体相对立的一种架构,它要求我们重新考虑数据的处理位置和所有权。

  • 数据网格规定,数据域应以一种易于消费的方式托管以及提供域数据集,而不是让数据从不同的域流向一个集中式的数据湖或平台。

  • 自助服务平台设计

  • 根据2020年DevOps现状调查报告,DevOps 的高度发展与自主服务能力密切相关,因为它使得团队更高效,控制力更强,而平台团队可以专注于持续改进基础设施。

  • 在这种情况下,Saxo 公司的自助服务平台已经超越了纯粹的基础设施,专注于使域团队能够发布自己的数据资产,以及使用其他团队发布的数据资产。

  • 数据与产品思维相融合

  • 在 Saxo,我们认为“数据即产品”,并且相信,产品的可用性与数据发现、理解和使用的难易度直接相关。我们鼓励域数据团队将产品思维运用到他们提供的数据集上,并且要将这项能力与其他能力一样严肃对待。

分布式领域驱动架构

Saxo 公司的主要数据域和其他投资银行或经纪公司没什么不同。生产者相关的域如 trading 表示企业的交易(事实),主数据集如 party 提供此类域的上下文,消费者相关的域如 risk 往往会消费大量的数据,但生成的数据很少(如指标)。



图 1:数据域

 

考虑到整个组织的变化节奏,我们知道,不能依靠一个集中式团队来创建和填充一个典型的企业级数据模型。我们的方法必须能够扩展。因此,我们将领域数据的所有权和它们的表示联合起来,进行集中管理。

 

其中的挑战在于,我们要确保整体大于部分(域会相互“啮合”,而非单独存在)的总和。图 2 是 Saxo 的数据运营模型。我们的治理目标是“恰好够用”就行,即可以做到以下几点:


  • 消费者与生产者解耦(事件优先于命令)。

  • 可以识别双方都认可的权威数据源。

  • 要有一种标准的语言来确保信息在企业里得到有效的使用;这种标准的或 "通用 "的语言是领域驱动设计(DDD)理念的核心,是消除开发人员和领域专家之间的沟通障碍的一种方法。

  • 其中,通用领域发挥着重要的作用,因为我们希望可以标准化银行中使用的一些基本概念。

 

我们是和数据办公室的同事一起做这项工作,我们将此看成是一个好机会,可以恰当地锚定所有权(一般是在企业里),并就每个领域的数据问题和策略展开对话。



图 2:数据运营模型

 

  • 领域团队负责:

  • 识别数据集的权威数据源

  • 创建相关数据模型;

  • 通过数据基础结构将数据产品提供给其他团队;

  • 修复数据质量(DQ)问题;问题应该在源头处理,而不是在消费时。

  • 企业数据架构师负责:

  • 策划和设计,使领域数据成为对 Saxo 银行来说有长期价值的东西;

  • 领域物理模型变更审批;

  • 与领域团队合作开发概念模型。

  • 数据治理办公室负责:

  • “激活”数据域,包括识别所有权、已知的数据质量问题等。

 

领域语言主要用于概念模型(挑战在于要尽量轻量化)和物理模型(使用元数据进行修饰)。我们并不关心如何从概念图生成物理模型,因为我们认为,这会将关注点从领域推理转移到可视化编程上。

 

这个过程并不简单,而且我们也只是刚刚开始。评审步骤无疑很重要,培训和建立实践社区也一样。命名合理、文档齐全、语义强类型、变化较小的模式很快就会得到批准。没能做到这些的模式必然就需要更长的时间。

 

此外,在这个过程中,我们还认识到,我们不可能第一次就做对,领域模型需要持续的完善。事实上,企业数据架构师的角色也可以称为 "数据保管员"。学习、迭代和改进。

 

当然,运营模型是动态的,只是作为某一时点的参考。

自助服务平台设计

数据网格是一种技术无关的架构范式。在 Saxo,我们使用 Confluent 作为数据基础结构的基础层——数据域的权威接口,以及比较传统的请求-响应接口。

 

虽然 Apache Kafka®基于日志的方式比较简单,但是将其作为企业中枢引入还是存在诸多挑战。我们希望领域团队可以不必考虑部署连接器、生成语言绑定以及如何处理个人身份数据等问题。考虑到开发平台较多(以 C#为主,但与我们合作的团队中也有使用 Python、C++以及 Kotlin 的),这不是件容易的事。



图 3:Saxo 银行的架构

 

我们的自助服务能力在很大程度上依赖于GitOps,每个数据领域都通过以下两个库进行管理:

  • 运营库(Operational)

  • 主题配置,包括 ACL;

  • Kafka 连接器管理;

  • 数据质量规则,使用Great Expectations DSL 表示。

  • 模式库(Schema)

  • Protobuf 模式验证和审批;

  • 为所有支持的语言生成代码绑定,并发布到我们的工件库。

 

可发现性不仅仅是指数据的结构,也是指让整个银行的数据专家了解数据产品的脉络、所有权和相对健康状况(表示为一系列量化指标)。

 

为此,元数据和指标都会发布到数据工作台——基于 LinkedIn 的开源项目DataHub实现——使整个银行的数据专家都能够看到发布到 Apache Kafka 的数据资产以及它们之间的关系。这方面还有很大的改进余地,例如,在工具中管理元数据,而不是直接在模式文件中。

数据与产品思维相融合

(数据)产品的可用性可以归结为易于发现、理解和消费。集中式架构的一个优点是,很容易在不同的数据域中保持一致的用户体验,并确保用户能够将他们的心理模型从一个域迁移到另一个域。但是,我们的联合架构需要一种完全不同的方法。

 

因此,在 Saxo,对于 Kafka 的使用,我们的自助服务平台有自己的独特之处。标准管道提供了一种跨所有领域的通用方法——样式检查、代码绑定生成、数据质量规则执行/报告,以及元数据如何推送到数据工作台等等。

 

我们尽量使数据资产能够自我描述,并且概念描述清晰。我们的想法是在域内和域间使用同样的元数据,提高数据的可用性,缩短新数据产品的上市时间。从根本上说,生产者的数据形状预期不会有很大的变化,至少开始时是这样。因为很小的变化就会产生很大的影响:

  • 各领域都采用同样的风格指南;

  • 特别注重强类型(“Currency”具有“string”所不具有的业务意义);

  • 只要可能就遵循行业标准;

  • 任何东西都有文档记录;

  • 禁用魔术值;

  • 从源头获取信息分类;

  • 链接相关概念。

 

然后,这些信息会通过数据工作台暴露出来。在我们的实现中,数据工作台扮演着重要的角色,不仅可以用于发现数据资产,而且让我们可以从意义、所有权和质量方面了解每个数据域和资产,实现持续改进。

有效的模式

那么,我们如何保证每个数据域的数据在外观上保持一致呢?接下来的部分将介绍我们建议团队在设计数据契约时参考的最佳实践,首先从消息格式开始。

选择一种格式

关于结构化数据的不同序列化机制的优点,已经有很多文章讨论过了,例如 Martin Kleppmann 的文章“Avro中的模式演变、Protocol Buffer以及Thrift”。通常,这些文章关注的都是模式管理和编码效率。

 

我们经常忽略的是,语义注释(又称元数据)很容易嵌入到模式中。虽然 XSD(XML 的模式定义语言)的口碑并不好,但事实证明,像FpML这样的标准在全球银行业中的应用非常成功,因为与其他方式相比,它们对消息定义有更严格的要求。事实上,鉴于 FpML 的开放性,许多人都以它为基础定义了自己的通用语言。

 

然而,由于 XML 已经不再是主流,我们研究了替代方案——特别是目前 Confluent 支持的方案:Avro、JSON 和 Protocol Buffer(Protobuf)。

 

在研究使用 JSON 编码的可行性时,FpML架构师工作组指出,用 JSON 根本无法表示同等多样化的数据类型和语言约束。而且,小数需要编码成字符串,这是唯一可靠的方式。此外,JSON Schema 没有提供表达自定义语义注释的方法。由于这些原因,我们没有考虑使用 JSON Schema。

 

在这方面,Avro 的表现略胜一筹,尤其是与 Avro 接口定义语言(IDL)结合使用时,还提供了模式可组合性。我们可以将语义注释表示成弱类型的 name-value 对,为类型和字段添加额外的属性。尽管 Avro 只定义了很少几个原语,但经过扩展后,该语言已包含许多核心逻辑类型(小数、UUID、日期和时间)。

 

Protobuf 更进一步,允许通过 "自定义选项 "实现强消息类型和字段级注释。这使得编译时检查成为可能,这无疑是很好的。

 

另一个考虑是语言绑定的成熟度。Saxo 最开始选择了 Avro,虽然不情愿,但我们认识到,这会成为主要的推广障碍。尽管我们确实贡献了一些修复(感谢 Matt Howlett 的支持),但 C#和 Python 实现还是落后于 JVM,我们觉得,围绕 C#实现获取支持会分散我们的注意力。

 

与 Avro 相比,Protobuf 的另一个优点是,对于类型和属性,绑定将遵循目标语言的风格规范,而不受模式中使用的命名规范所影响。虽然这看起来是个小问题,但如果不适当地加以考虑,它就会成为另一个阻力源。

 

2020 年底,我们最终切换到了 Protobuf,在 Confluent Schema Registry 将其作为一等公民支持之后不久。当然,对于我们感兴趣的语言绑定(C#、Python、C/C++,随着 Kafka Streams 的关注度增加,还有 JVM),我们发现,这些实现要比 Avro 的一致性更好。

自描述模式

即使不了解 Protobuf 也没关系,只要你掌握了错综复杂的 XSD,就会发现这很简单。

 

简单来说,复杂类型会被表示为 "消息",而不管它是一个事件还是一个被事件引用的类型。尽管语法略有不同,但 "选项"(即语义注释)可以在消息(类型)或字段(属性)层面上表达。更多细节,请参阅《Proto3语言指南》。

命名

命名很难,不过可以使用Uber的风格指南。该指南可以提供很好的一致性,并提供一种完善的方法来进行版本管理。

 

我们还有许多你在任何编码标准中可能都会看到的准则。单数值的名字应该是单数的,复数字段的名字应该是复数的,等等。

文档

所有记录和属性都需要在文档中说明。即使看上去明显的字段也经常有些细节不够明显。

标识符

企业标识符保持一致,是这种分布式模式发挥作用的关键要求之一。毕竟,考虑到Mesh这个词的含义:"在正确的位置连接在一起"。标识符可以唯一地标识一个实体,可以认为是领域的 "主键",这也是我们实现数据网格的基本原则之一。

 

标识符是用 Protobuf IDL 定义的,如下所示:

// 以引用方式传递“Thing”message ThingIdentifier {    int64 id = 1;}
复制代码

按照惯例,对于标识符的唯一属性,我们将数字标识符命名为id,字母数字并用的标识符命名为code。对于各领域都有的企业标识符,必须以一种共同的方式来定义。

引用

引用(References )可以看成替代标识符,约束相对弱一些。例如,PaymentReference可能是一个由客户提供的自由格式的文本字段。下面是一个例子:

// 用户提供的引用。不一定唯一。message ThingReference {    string ref = 1;}
复制代码

按照惯例,对于引用的唯一属性,我们将其命名为ref(通常是string类型)。企业引用必须以一种共同的方式来定义。

枚举和方案

有些数据元素的值被限制为只能是一组有限可能值中的一个。通常,这种有限值集被称为枚举。

 

和许多其他语言类似,Protobuf 也支持枚举类型。如果值的个数很少(例如小于 10 个),并且预计不会经常改变,那么就可以使用enum类型。不过,一般来说,人们会希望类型参考一个外部编码方案(coding scheme)这个概念来自FpML)。

 

如下所示,在 IDL 中引用方案:

// 根据ISO 3166标准,用三个字符的字母代码表示货币。// https://www.iso.org/iso-4217-currency-codes.html// https://spec.edmcouncil.org/fibo/ontology/FND/Accounting/CurrencyAmount/Currencymessage CurrencyIdentifier {    option (metadata.coding_scheme) = "topic://reference-currency-compact-v1";    // 字母代码(三个字母)    string code = 1;}
复制代码

在这个例子中,读者被导向一个简明的主题reference-currency-compact-v1,这是货币代码及其含义的权威列表。

 

方案不仅可以帮助接收方理解取值范围,而且还为数据质量自动监控创造了条件。理想情况下,模式会参考另一个主题,但如果团队参考文档,我们也会很高兴。

信息分类

Saxo 定义了四种信息分类,每一类的敏感度都各不相同,模式中有体现,如下所示:

// 自然人的姓名message Name {    option (metadata.msg_info_class) = INFO_CLASS_PERSONAL;    // 一个人的姓氏或名字,出生时就已经选定,或者由出生时选定的名字修改而来    string given_name = 1;}
复制代码

尽管我们希望体现在消息层面上,但有时候在在字段层面上进行表达可能更合理。在这种情况下,就要指定field_info_class选项。如果没有说明,就会默认为”仅内部 (internal only)“分类。

 

我们上云的一项关键工作是确保 PII 数据经过加密。我们的最终目的是,直接通过模式注释驱动加密,使开发团队无需关注这些细节。

外部模式

虽然我们制定了关于 Protobuf 的标准,在某些情况下,我们也支持“引入自己的模式”。在这种情况下,具体的模式需要通过external_schema选项显式引用:

// 外部模式示例message EventWithExternalSchema {    XmlString vendor_string = 1 [(metadata.external_schema) = "https://example/third-party.xsd"];}
复制代码

请注意,虽然由vendor_string表示的负载可能包含third-party.xsd的引用,但为了便于“设计时”使用,必须在元数据中显式引用。

弃用

弃用是演进过程中的必然产物,弃用可以让消费者针对破坏性更改做好未来规划。弃用可以在字段层或消息层上表示,如下所示:

// 属性弃用示例message EventWithDeprecatedField {    // 当时看似乎是个好主意,但后续可能会删除(或保留)。    int32 old_field = 1 [deprecated = true];    // 更好的方法    int64 new_field = 2;}
复制代码

外部标准

应该如何表示一个电子邮件地址?一个日期?一个产品?一份监管文件?有可能已经有标准,有的话,我们就可以直接用。

 

只要可行,我们就会在文档中引用这些标准,有时是作为唯一定义,有时涉及 Saxo 的实现。我们通过 "业务术语 "选项,使用term_sourceterm_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的定义直接使用了金融业商业本体(Financial Industry Business Ontology)中的同名术语。

术语链接

在我们迭代领域模型时,有些概念可能会在不同的数据域中重复。这是无意的,但也是不可避免的,不管前期再怎么设计。为了避免破坏性更改,我们需要一种机制,让我们可以在不影响现有生产者或消费者的情况下引用已有的概念。

 

借助field_term_link选项,我们可以将领域模型中的元素链接到行业术语的权威定义:

// “行业术语”链接示例message EventWithLinkedTerm {    // 交易币    string trade_currency = 1 [(metadata.field_term_link) = "CurrencyIdentifer"];}
复制代码

在这个例子中,trade_currency引用了 "货币 "的通用表示,而且不会影响模式的兼容性。这不仅可以用来改进文档,而且还可以作为一份备忘录,用于领域模型未来的迭代。

衡量标准

这种设计的价值在于,让我们具备了进一步利用数据的能力。我们如何知道这条路是否正确?为此,Saxo 已经识别出了一些指标,用于衡量愿景实现的进展。

  • 网格中的连接数量(一个没有连接的领域不可能产生广泛的价值)

  • 生产者:消费比例

  • 创建满足消费者需求的数据产品的提前期

  • 数据产品指标(如数据质量覆盖率趋势)

  • 测试覆盖率(对变更的适应性)

  • 等等

未来展望

尽管 Saxo 已经在这些想法上做了一段时间的迭代,但只是在最近,在我们设法应对扩展挑战的过程中,这种实现的应用才逐渐增多。我们主要关注的是:


  • 继续降低进入平台的障碍;

  • 提高平台的附加值,例如通过自动对账框架;

  • 和数据室以及整个银行的同事一起工作,培养领域思维;

  • 继续与Acryl以及LinkedIn DataHub社区合作,力争使数据工作台成为所有数据专家的一站式平台:

  • 以领域为中心的用户界面;

  • 领域健康游戏化;

  • 利用众包方式改进模式文档;

  • 在用户界面中展示数据质量规则和结果;

  • 融合所有其他的上下游平台;

  • 使团队可以方便地使用类似 ksqlDB 这样的工具。

 

如果想了解更多信息,可以观看我的流式音频播客,我在里面更详细地讨论了这篇文章的内容。

 

查看英文原文:Saxo Bank’s Best Practices for a Distributed Domain-Driven Architecture Founded on the Data Mesh


2021 年 7 月 20 日 15:381

评论

发布
暂无评论
发现更多内容

腾讯某Java程序员为了肝出《300页图解网络知识》+《计算机底层操作系统》超全教程差点猝死!

Java架构之路

Java 程序员 面试 编程语言 操作系统

第二周作业二-学习总结

道长

极客大学架构师训练营

开放的是金融服务 必须确保持牌经营

CECBC区块链专委会

金融 银行

数据结构与算法:递归

Java架构师迁哥

Java 编程 程序员 算法

2020年高频Java面试题集锦(含答案),让你的面试之路畅通无阻!

Java架构之路

Java 程序员 面试 编程语言

架构师第二周作业

悠哉

极客大学架构师训练营

const关键字应用总结

C语言与CPP编程

程序员 编程语言 C语言

区块链+跨境贸易:让跨境数据高效共享可信

CECBC区块链专委会

区块链 跨境贸易 跨境电子商务

最新整理国内知名大厂7篇Java岗面试真题,奥利给!

Java架构师迁哥

第二周 作业一【未陌】

a d e

设计模式 架构设计原则 基本原则

flutter 中的video player对比学习

Daniel

听说有人不了解柔性数组

C语言与CPP编程

程序员 数组 编程语言 C语言

架构师第二周总结

悠哉

原来我写的软件里面都是臭味 - 架构师训练营第 1 期 - 第二周总结

Todd-Lee

极客大学架构师训练营

极客时间架构师培训 1 期 - 第 2 周作业

Kaven

第二周作业

华美而火锅

超全算法笔试模拟题精解合集,这份《程序员面试宝典》简直太牛了

Java架构之路

Java 数据结构 面试 算法 编程语言

C语言/C++基本语句编程风格

C语言与CPP编程

程序员 编程语言 C语言

食堂就餐卡系统设计

谭明华

极客大学架构师训练营

第二周 作业二:框架设计学习总结【未陌】

a d e

设计模式 架构设计

架构师训练营 - week1 - 个人学习心得总结

谭明华

第二周作业

饭桶

小白也能看懂的REDIS教学基础篇——REDIS基础数据结构

云流

Java 数据库 redis

linux虚拟摄像头vivid配置

良知犹存

Linux 虚拟摄像

第二周学习总结

饭桶

架构师训练营第二周学习总结

Gosling

极客大学架构师训练营

架构师训练营 - week2 - 个人学习心得总结

谭明华

极客大学架构师训练营

框架设计-第二周作业

睁眼看世界

极客大学架构师训练营 软件设计原则

UML练习2

何毅曦

架构1期-第二周作业一

道长

极客大学架构师训练营

周总结二

何毅曦

云原生场景下企业API 网关选型及落地实践

云原生场景下企业API 网关选型及落地实践

基于Data Mesh构建分布式领域驱动架构的最佳实践-InfoQ