原文:
towardsdatascience.com/deliver-your-data-as-a-product-but-not-as-an-application-99c4af23c0fb
为了将你的数据作为产品交付,而不仅仅是作为一个没有进一步业务背景的表格或文件,这是数据网格框架的一个关键原则。考虑是否通过应用程序服务(API)或作为纯数据结构来交付数据作为产品是一个重要的设计决策。我之前在我的关于数据网格的三部分系列的第二部分中考察了这一特定挑战数据网格中的挑战和解决方案。然而,本文将讨论数据网格概念之外的问题,因为我认为这是如此基础。我将概述关键差异,然后论证为什么你应该更喜欢“数据作为纯结构”而不是“数据作为应用程序”。
数据作为产品
在数据工程领域,将数据转化为产品的概念并不新鲜,甚至在数据网格框架定义之前就已经被使用。然而,在创建由数据驱动的产品和处理数据本身作为产品之间有一个重要的区别——这里是对“数据作为产品”和“数据产品”之间微妙差异的很好解释。在这篇文章中,我专注于“数据作为产品”,即使我也为了简洁使用了“数据产品”这个术语。
大多数关于数据产品概念的文章都描述了“数据作为应用程序”的方法。这是不幸的,因为与“数据作为纯结构”的方法相比,这种方法存在显著的缺点。独立于数据产品的概念,Yehonathan Sharvit 在其名为“面向数据编程”(DOP)的书中描述了使用纯数据结构的原理:
-
将代码(行为)与数据分离。
-
将数据视为不可变的。
-
将数据模式与数据表示分离。
-
使用通用数据结构表示数据。
虽然后三个原则在企业层面是高度推荐的最佳实践,但遵守第一个原则对于确保数据产品可以在没有应用程序的情况下存在至关重要。但让我们首先详细探讨这些差异。
数据作为应用程序
Image by author
在“数据作为应用程序”的方法中,数据通过一个接口(API)进行访问,允许客户端通过向应用程序实例发出 API 调用来检索数据。这个应用程序实例可以是一个定制的企业应用程序,提供特定的数据,甚至是一个完整的数据库或 AI 模型(LLMs 越来越受欢迎),提供相当通用的数据抽象。无论如何,都需要一个运行中的应用程序实例(“服务器”)来允许客户端访问数据。这个实例管理存储在底层文件中的数据,禁止通过操作系统调用或自定义库函数直接访问文件。相反,你必须使用应用程序实例提供的预定义接口。
数据作为纯结构
将代码(行为)与数据分离需要定义数据产品为纯数据结构。
数据和元数据打包在文件中,与应用程序分离——图片由作者提供
“数据作为纯结构”指的是独立于任何应用程序存在并存在于任何应用程序之外的数据。它包括将原始数据转换为产品所需的所有元数据,但仍然是独立于伴随应用程序的纯数据结构。这个数据结构可以在不向应用程序实例发出 API 调用的情况下访问。例如,在类 Unix 系统中,这样的结构会被存储为一个文件(本质上是一个字节流),可以通过操作系统调用直接访问。尽管这些在技术上属于系统调用,但它们与 API 调用不同,因为如果没有运行的操作系统的支持,你实际上什么都不能做。
通常,我们在操作系统的底层维护一个分布式文件系统的版本,以实现分布式文件访问,例如 Hadoop 中的 HDFS。这一层(如果你喜欢,可以将其视为数据产品存储基础设施)与操作系统的集成非常紧密,而不是作为一个独立的应用程序运行。
重要的是要注意,除了它们产生和消费的数据产品外,应用程序可以选择性地包含它们自己的数据存储(图中的应用程序 4 和 5)。此外,请注意,这些数据产品包含关于它们使用和来源的全面信息。例如,纯结构 2包括由应用程序 2使用纯结构 1 和 3作为输入创建的详细信息。
其他高度推荐的原则
好的,我们决定将代码与数据分离。不深入探讨剩余原则如何具体实现——数据网格系列的第二部分有更多细节并且我肯定会在这未来的文章中更详细地讨论它——让我们探索当我们应用其他原则时所获得的额外价值。
-
将数据视为不可变 将数据视为不可变的信息块并不意味着数据不能被更改。这意味着对数据应用的所有更改或转换都会被保留。增加的价值是所有应用逻辑的完整溯源,直至信息源。
-
将数据模式与数据表示分离 如果我们不预先定义模式(数据表示),而是从数据本身推导它,我们将在我们的数据产品中获得极大的灵活性和适应性。例如,我们可以重用逻辑在不同的内容上,自动从数据中推导出最新的数据模型,包括模型的所有变更历史。
-
使用通用数据结构表示数据 即使是非常复杂的数据结构也可以通过增量应用转换逻辑从更简单、更通用的数据结构中组装出来。最通用的结构是数据原子——在定义数据原子时一个有趣的概念是 Lars Rönnbäck 提出的Posit。如果我们也使用相同的基本数据原子来描述原始数据的业务背景,我们可以在数据产品中显著简化并通用化数据和元数据管理。
什么是应用程序陷阱?
数据和应用程序协同工作,有效地数字化我们的业务流程。应用程序实现必要的业务逻辑,而业务对象则以数据的形式存储。没有应用程序的数据就像原油——充满潜在价值,但尚未可用。相反,没有数据的应用程序就像没有燃料的引擎——充满潜力,但无法运行。
因为这两个部分显然不能没有对方而单独工作,所以我们经常将它们捆绑在一起。这种捆绑的另一个原因可能是,今天的大多数 IT 工程师都是在面向对象编程(OOP)中受过培训。OOP 教导我们将操作数据的函数封装在对象中,这些函数为对象内部的原始数据添加业务背景。业务背景包括关于模式、内容、数据溯源、交付频率、质量指标以及其他解释数据特征和使用数据方式的宝贵元数据。为了与业务背景一起交换数据,我们需要完整的对象;否则,我们会失去关键的业务信息。
由于应用程序在面向对象编程(OOP)中表现为实例化的对象,我们倾向于设计“数据作为应用程序”。
为什么数据是不同的
让我们来看看这个设计决策的影响。应用程序可以被看作是在特定环境中运行的实例化对象。例如,用 Java、Scala 或 Kotlin 编写的应用程序在 Java 虚拟机(JVM)中运行,而对于用 C、C++或 Rust 等语言通过系统调用的应用程序,其执行环境是操作系统。每当我们需要在这些环境之间进行交互——由于类型不同或因为它们运行在不同的节点上——我们通常不会在这些环境之间传输整个应用程序。相反,我们使用基础设施服务(如网络和协议,如 HTTP),允许一个应用程序从另一个不同执行环境中的应用程序调用函数(API 调用)。
当数据被提取时,业务上下文或语义信息会迅速丢失——图片由作者提供
我们有返回业务信息的函数和返回原始数据的函数。因此,我们可以轻松地从应用程序中提取或分离原始数据,并与他人交换。然而,这样做会剥离关键的业务上下文。接收应用程序只得到原始数据,缺乏关于其来源和详细含义的信息。
为了防止这种信息丢失,我们还必须包括一个指向原始应用程序的引用,从该应用程序中提取了数据。虽然从理论上讲是可能的,但这种方法提出了几个实际挑战:
-
由于数据非常灵活,它很快就会失去与提供业务上下文的应用程序的连接。使用 BI 应用程序轻松聚合和转换原始数据或使用 AI 模型推导出一些智能是非常容易的,但维护包括数据血缘在内的完整业务上下文则要困难得多。当数据通过许多不同的转换步骤流动时,大多数应用程序无法处理对源应用程序和业务上下文的额外引用,因此业务上下文会丢失。
-
数据的寿命比应用程序长得多。数据可以无限期存储,这是规则而不是例外,而应用程序可能每 5 到 10 年就会被替换一次。很可能在你的存储系统中,有数据是由那些操作系统和所需硬件已不再可用以执行它们的应用程序提供的。
-
在不同环境之间传输应用程序逻辑非常困难,而用数据来做这一点则极其容易。是的,你可以序列化例如 Java 或 Python 应用程序,并在相同类型和版本的另一个环境中反序列化它们。但尝试序列化在 z/OS 上运行的旧 Cobol 程序并在现代类 Unix 系统上反序列化它——你将面临挑战。
-
跟踪数据的历史和不同版本是一种相当普遍和广泛的做法。然而,对运行中的应用程序实例做同样的事情则属于例外而非规则。是的,你可以在代码仓库中跟踪源代码。随着现代容器技术的发展,你甚至可以跟踪所有编译的应用程序版本并将它们存储在镜像注册表中。但关于所有那些仍然广泛使用的较老和传统应用程序怎么办?在实践中,我从未见过一个系统能够持续跟踪所有应用程序版本,并在客户端需要某个时间点的业务上下文数据时,有效地运行旧版本的应用程序。
-
API 有效地预定义了你可以从应用程序中检索信息的方式。对于原始 API 定义中没有提供的数据需求,你需要实现额外的 API 调用。或者,你可以尝试设计一个极其通用的 API,以便覆盖所有不可预测的未来数据使用类型。这最终导致应用程序变得像通用数据库。但即使是特定类型的数据库(例如关系型数据库或向量数据库)也不足以涵盖所有今天的数据需求——NoSQL 数据库的流行是这一事实的明显迹象。
数据不应像应用程序那样被对待,因为它具有这些显著不同的特征。虽然应用程序对于格式化、聚合和转换数据以提取价值是必不可少的,但数据产品本身并不是应用程序。补充你的数据产品以额外的应用程序或库函数来简化其使用是有益的。但请记住,不可预测的需求种类可能会迅速迫使你开发“万能”数据库或 AI 应用程序,而实际上还没有人能够真正实现这一点。
因此,“数据作为纯结构”的方法似乎最有前途。它将数据作为具有丰富业务背景的独立结构维护,允许灵活且直接访问,无需特定应用程序实例。它防止了试图创建全面服务器应用程序所带来的复杂性,并确保数据在各种环境和上下文中保持灵活和可用。
为了确保你的数据产品能够被普遍访问,使用开源数据结构格式是必不可少的。流行的格式包括:
-
Arrow 是一种列式内存数据格式,越来越受欢迎,用于在不同编程语言和环境之间交换数据。
-
Parquet 是一种针对磁盘和分布式存储系统(如 HDFS)优化的列式数据格式。
-
Avro 是一种针对磁盘和分布式存储系统(如 HDFS)优化的行式数据格式。
-
JSON (JavaScript Object Notation) 一种非常轻量级的数据格式,主要用于应用服务器和 Web 客户端之间的数据交换——基于文本且易于阅读,但不是大数据量的最佳解决方案。
-
协议缓冲区 (Protobuf) 这是谷歌对一种面向行数据格式的看法,该格式优化了在磁盘和分布式系统上的存储。
根据你的使用场景,还有许多其他格式可供选择——只需记住,它应该是开放的并且被广泛使用的。
如果你想了解更多关于数据工程挑战和与数据网格(数据产品在第二部分讨论)相关的解决方案,请参阅我关于这个主题的三部分系列:

被折叠的 条评论
为什么被折叠?



