作者 | GRAHAM STIRLING
译者 | 平川
审校 | 蔡芳芳
虽然遵守 GDPR 和 BCBS 239 等法规可能是一项挑战,但它们只不过是现代数据平台的最佳实践指南。一个有远见的组织应该有一个数据基础结构(data fabric ),以解决常见的非功能需求,同时还要有一个运营模式,以识别数据的战略价值。本文介绍 Saxo 银行如何借助数据网格架构来实现这一愿景。在数据平台团队的推动下,我们彻底地重新思考了组织内的数据使用情况。
本文最初发布于 Confluent 官方博客,经原作者授权由 InfoQ 中文站翻译并分享。
“所有数据归人民”,这在企业里是一个非常有吸引力的主张。然而,对于许多组织来说,快速解决集成问题,将数据提供给需要的人来解锁洞察和创新还是一个遥远的梦。
虽然遵守 GDPR 和 BCBS 239 等法规可能是一项挑战,但它们只不过是现代数据平台的最佳实践指南。一个有远见的组织应该有一个数据基础结构(data fabric ),以解决常见的非功能需求,同时还要有一个运营模式,以识别数据的战略价值。
本文介绍 Saxo 银行如何借助数据网格架构来实现这一愿景。在数据平台团队的推动下,我们彻底地重新思考了组织内的数据使用情况。
大规模分布式数据管理
虽然在过去二十年中,科技行业在大规模数据处理方面已经取得了长足的进步,但在很大程度上,仍然未能将数据管理扩展到组织层面。这既是工作方式的问题,也是技术变革的问题。简而言之,从现在开始,我们要把数据当作一种产品来考虑,并考虑它是否容易发布、发现、理解和消费。
自 2019 年 Zhamak Delgahni 首次提出这一概念以来,作为一种架构范式,数据网格逐步获得了人们的关注。2018 年底,Saxo 银行开始实现一种新的数据架构,走上了类似的道路,我们很快就意识到,我们思路与数据网格非常相近。
围绕 Delgahni 最初的设想,相关的文献资料已经很多,本文不会再做这方面的讨论。取而代之,我们将大概介绍下 Saxo 公司如何从数据网格的关键原则入手探索这一架构范式,如何将其变成现实,以及还面临什么挑战。
分布式领域驱动架构
自助服务平台设计
数据与产品思维相融合
分布式领域驱动架构
Saxo 公司的主要数据域和其他投资银行或经纪公司没什么不同。生产者相关的域如 trading 表示企业的交易(事实),主数据集如 party 提供此类域的上下文,消费者相关的域如 risk 往往会消费大量的数据,但生成的数据很少(如指标)。
图 1:数据域
考虑到整个组织的变化节奏,我们知道,不能依靠一个集中式团队来创建和填充一个典型的企业级数据模型。我们的方法必须能够扩展。因此,我们将领域数据的所有权和它们的表示联合起来,进行集中管理。
其中的挑战在于,我们要确保整体大于部分(域会相互“啮合”,而非单独存在)的总和。图 2 是 Saxo 的数据运营模型。我们的治理目标是“恰好够用”就行,即可以做到以下几点:
我们是和数据办公室的同事一起做这项工作,我们将此看成是一个好机会,可以恰当地锚定所有权(一般是在企业里),并就每个领域的数据问题和策略展开对话。
图 2:数据运营模型
1、领域团队负责:
2、企业数据架构师负责:
3、数据治理办公室负责:
领域语言主要用于概念模型(挑战在于要尽量轻量化)和物理模型(使用元数据进行修饰)。我们并不关心如何从概念图生成物理模型,因为我们认为,这会将关注点从领域推理转移到可视化编程上。
这个过程并不简单,而且我们也只是刚刚开始。评审步骤无疑很重要,培训和建立实践社区也一样。命名合理、文档齐全、语义强类型、变化较小的模式很快就会得到批准。没能做到这些的模式必然就需要更长的时间。
此外,在这个过程中,我们还认识到,我们不可能第一次就做对,领域模型需要持续的完善。事实上,企业数据架构师的角色也可以称为 "数据保管员"。学习、迭代和改进。
当然,运营模型是动态的,只是作为某一时点的参考。
自助服务平台设计
数据网格是一种技术无关的架构范式。在 Saxo,我们使用 Confluent 作为数据基础结构的基础层——数据域的权威接口,以及比较传统的请求 - 响应接口。
虽然 Apache Kafka®基于日志的方式比较简单,但是将其作为企业中枢引入还是存在诸多挑战。我们希望领域团队可以不必考虑部署连接器、生成语言绑定以及如何处理个人身份数据等问题。考虑到开发平台较多(以 C# 为主,但与我们合作的团队中也有使用 Python、C++ 以及 Kotlin 的),这不是件容易的事。
图 3:Saxo 银行的架构
我们的自助服务能力在很大程度上依赖于 GitOps,每个数据领域都通过以下两个库进行管理:
a、运营库(Operational)
b、模式库(Schema)
可发现性不仅仅是指数据的结构,也是指让整个银行的数据专家了解数据产品的脉络、所有权和相对健康状况(表示为一系列量化指标)。
为此,元数据和指标都会发布到数据工作台——基于 LinkedIn 的开源项目 DataHub 实现——使整个银行的数据专家都能够看到发布到 Apache Kafka 的数据资产以及它们之间的关系。这方面还有很大的改进余地,例如,在工具中管理元数据,而不是直接在模式文件中。
数据与产品思维相融合
(数据)产品的可用性可以归结为易于发现、理解和消费。集中式架构的一个优点是,很容易在不同的数据域中保持一致的用户体验,并确保用户能够将他们的心理模型从一个域迁移到另一个域。但是,我们的联合架构需要一种完全不同的方法。
因此,在 Saxo,对于 Kafka 的使用,我们的自助服务平台有自己的独特之处。标准管道提供了一种跨所有领域的通用方法——样式检查、代码绑定生成、数据质量规则执行 / 报告,以及元数据如何推送到数据工作台等等。
我们尽量使数据资产能够自我描述,并且概念描述清晰。我们的想法是在域内和域间使用同样的元数据,提高数据的可用性,缩短新数据产品的上市时间。从根本上说,生产者的数据形状预期不会有很大的变化,至少开始时是这样。因为很小的变化就会产生很大的影响:
然后,这些信息会通过数据工作台暴露出来。在我们的实现中,数据工作台扮演着重要的角色,不仅可以用于发现数据资产,而且让我们可以从意义、所有权和质量方面了解每个数据域和资产,实现持续改进。
有效的模式
那么,我们如何保证每个数据域的数据在外观上保持一致呢?接下来的部分将介绍我们建议团队在设计数据契约时参考的最佳实践,首先从消息格式开始。
选择一种格式
关于结构化数据的不同序列化机制的优点,已经有很多文章讨论过了,例如 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_source 和 term_ref 选项,将领域模型与外部标准联系起来,如下所示:
// 属性弃用示例message EventWithDeprecatedField { // 当时看似乎是个好主意,但后续可能会删除(或保留)。 int32 old_field = 1 [deprecated = true]; // 更好的方法 int64 new_field = 2;}
显然,在这个例子中,MonetaryAmount 的定义直接使用了金融业商业本体(Financial Industry Business Ontology)中的同名术语。11
术语链接
在我们迭代领域模型时,有些概念可能会在不同的数据域中重复。这是无意的,但也是不可避免的,不管前期再怎么设计。为了避免破坏性更改,我们需要一种机制,让我们可以在不影响现有生产者或消费者的情况下引用已有的概念。
借助 field_term_link 选项,我们可以将领域模型中的元素链接到行业术语的权威定义:
// “行业术语”链接示例message EventWithLinkedTerm { // 交易币 string trade_currency = 1 [(metadata.field_term_link) = "CurrencyIdentifer"];}
在这个例子中,trade_currency 引用了 "货币 "的通用表示,而且不会影响模式的兼容性。这不仅可以用来改进文档,而且还可以作为一份备忘录,用于领域模型未来的迭代。
衡量标准
这种设计的价值在于,让我们具备了进一步利用数据的能力。我们如何知道这条路是否正确?为此,Saxo 已经识别出了一些指标,用于衡量愿景实现的进展。
未来展望
尽管 Saxo 已经在这些想法上做了一段时间的迭代,但只是在最近,在我们设法应对扩展挑战的过程中,这种实现的应用才逐渐增多。我们主要关注的是:
如果想了解更多信息,可以观看我的流式音频播客,我在里面更详细地讨论了这篇文章的内容。
查看英文原文:
Saxo Bank’s Best Practices for a Distributed Domain-Driven Architecture Founded on the Data Mesh
今日好文推荐
中国移动翼龙无人机为河南受灾地区提供网络;阿里云抄袭官司达成和解;华为云电脑停止服务和运营:数据将永久删除 | Q资讯
谷歌开发团队犯低级错误?因一个字符拼写Bug,Chromebook用户被锁在系统之外
InfoQ 读者交流群上线啦!各位小伙伴可以扫描下方二维码,添加 InfoQ 小助手,回复关键字“进群”申请入群。回复“资料”,获取资料包传送门,注册 InfoQ 网站后,可以任意领取一门极客时间课程,免费滴!大家可以和 InfoQ 读者一起畅所欲言,和编辑们零距离接触,超值的技术礼包等你领取,还有超值活动等你参加,快来加入我们吧!
点个在看少个 bug 👇