数据连接与集成构建管道Best practices开发最佳实践

注意:以下翻译的准确性尚未经过验证。这是使用 AIP ↗ 从原始英文文本进行的机器翻译。

开发最佳实践

本指南旨在为开发变换的数据管道开发者提供指导。通用建议同样可能引起项目经理或平台管理员的兴趣,因为它们专注于清晰管道的高阶原则。

通用最佳实践

管道开发即软件开发

许多来自通用软件开发的最佳实践同样适用于定义组成数据管道的变换。以下是一些体现此方法的常见实践:

  • 减少认知负担: 尽可能减少必要的思考量。如果需要使用复杂的函数或非显而易见的 API 调用,请确保有清晰的文档。
  • 善待未来的自己: 管道是数据企业的基础。在规划和实施时要考虑长远和高瞻远瞩。
  • 不要重复自己(DRY): 重复的代码或概念需要更多的维护,并导致细微的错误。相反,应在各个层次上经常重构,包括搭建和发布自己的库或包以跨存储库使用,以确保最小的重复。
  • 避免技术债务: 长期思考的一个推论是,技术债务在项目特定需求的截止日期面前很容易累积,但总会反过来影响。
  • 约定很重要: 设定一个先例并坚持下去;这减少了认知负担,并有助于代码存储库之间的易读性。例如,列名和数据集名称通常用 snake_case 编写——遵循这一约定意味着任何想引用你出色数据集的人都知道它是 awesome_dataset,而不是 AwesomeDatasetAwesome_Dataset
  • 少即是多: 由许多较小单元组成的系统比由少数较大单元组成的系统更易于维护和理解;因此倾向于:
    • 范围明确的 Foundry 项目
    • 更小的变换链在一起
    • 短函数 - 以及变换中的辅助函数

反模式

  • 覆盖 vs. 删除: 与其他文件系统不同,Foundry 资源和数据集连接到其他工件,而不仅仅是文件本身。因此,如果您在迭代管道或数据,只有在根本上想要删除该类型的数据时才应进行删除。例如,如果您将不正确的数据写入数据集,不要删除数据集;而是写一个新的 SNAPSHOT 事务来覆盖之前的。尝试删除数据集可能比在相同位置创建新数据集带来更多挑战。
  • 不要引入循环依赖: 如果您希望将变换的输出数据集用作其他变换的输入,您应确保未在代码中引入任何循环依赖。Foundry 的构建调度层将尝试阻止您在当前分支上配置任何循环,如果检测到循环,您的代码存储库分支将无法通过检查。

Foundry 将检查正在开发的分支上的循环依赖,但在编写代码时不会跨所有分支运行检查,例如如果存在仅在主分支上存在的 Ontology 数据输出数据集。当尝试将功能分支与包含循环依赖的分支合并时,如果在其他分支上检测到循环依赖,Foundry 仍将无法通过检查。

项目文件夹结构

查看推荐的项目结构,了解组织通过多个项目的数据流的总体模型。

最佳实践

  • 在项目级别管理权限: 如果您预计需要在项目内进一步划分权限,请考虑是否应将项目进一步分解为更小的部分。了解有关应用和管理权限的概念和实践的更多信息。
  • 保持项目范围明确: 避免“功能蔓延”以及在项目中添加无关的资源或用例特定的逻辑。
  • 使用有意义的文件夹名称: 记住,您正在设计一个项目供团队外的人使用以及用于日常迭代和开发。至少应有一个 /Documentation 文件夹和一个 /Output 文件夹。不同的用例或工作流项目将需要更具体的名称,但始终考虑到您的项目结构和命名方案是为访客设置的指路标。

反模式

  • 在个人文件夹中工作: 您的个人文件夹不是开发的地方——访问控制严格,共享困难。考虑在项目中创建一个 /scratch 文件夹用于实验或一次性工作,而不是在个人文件夹中构建。

命名

最佳实践

  • 遵循约定: 根据您组织的通用模式或您 Foundry 实例的约定命名列、数据集、存储库、文件和其他资源。
  • 选择描述性名称: 花时间构思两到三个字的名称,让读者了解所命名资源的目的或内容。考虑将名称的独特部分放在首位;这有助于在下拉列表中截断完整文本时,以及在扫描代码库或文件列表时立即区分引用。避免使用缩写。

反模式

  • 晦涩的名称: 选择简单地递增一个数字的名称(即 dataset1dataset2,等等)或单字母名称会使代码更难阅读和重构,并且对于新开发者来说更难接近您的工作。
  • 仅通过路径区分的名称: 在某些情况下这是可以接受的,例如当您有 /raw/my_important_dataset 然后是 /clean/my_important_dataset 时,然而在许多情况下,这种命名模式会在仅显示数据集名称的视图中造成混淆。请记住,来源是被跟踪并容易看到的,所以您无需将这种“状态”嵌入到数据集的名称中。

数据类型

最佳实践

显式转换列类型: 如果您正在数据源项目中工作,即使数据连接的架构推断选择了正确的值,也要在 rawclean 变换中显式转换列类型。这将有助于捕获源系统的列类型更改或无效值在同步期间导致不正确推断的破坏性更改。

模式

对“仅时间”数据类型使用时间戳: Spark 没有仅时间数据类型用于像“10:59:00”这样的字段。为了利用 Spark 的时间戳类型附带的时间函数,将值转换为秒,然后在将其转换为时间戳之前加上 -2208988800 以将其置于第 0 年。或者,将其保留为字符串,让用户根据需要解析。

反模式

  • 将所有数字字段转换为数值数据类型: 考虑一个飞机 ID 列,如 545972314。将这些转换为整数列可能很诱人(毕竟它们看起来像整数,甚至可能在源系统中是整数)。然而,这有显著的缺点:

    • 如果前导零有意义,例如 123 和 0123 都是合法 ID 应该区分,这将导致出错。
    • ID 可能在 UI 中以不理想的方式格式化(例如 545.01,234,右对齐)。
    • 如果 ID 太长,您可能会遇到 MAX_INT 问题。
    • 数字函数永远不会应用于这些值(例如,您永远不会将两个飞机 ID 相加)。规则是仅在适合对其进行算术运算时将数字字段转换为数值数据类型,否则将其转换为字符串。
  • 在不同时区存储时间戳: — Spark 时间戳是时区无关的。它们在内部以 UTC(也称为 Zulu,GMT)存储;显示时区预计由前端完成。

代码注释

反模式

  • 在提交中注释掉代码: 在进行更改时,可能很诱人地注释掉旧代码并保留以供参考或在需要时回滚。您可以在迭代时使用注释,但不要提交有注释语句的代码。这样做会产生冗余并减少可读性。旧代码很容易在先前的提交中找到。

  • 带有作者详细信息和日期的注释: 这些信息会在提交/git 存储库中自动跟踪。手动注释容易不被更新并产生冗余。

  • 过于冗长的注释: 注释应分享决策背后的理由而不是解释逻辑本身。力求编写“自文档化”代码;如果一组语句难以理解,这是一个明确的重构和简化信号。

存储库实践

最佳实践

  • 保护 master 分支: 如果您正在与团队一起开发,或者即使只是在一个长期存在的个人项目上工作,保护 master 分支并实践 GitFlow ↗ 或您首选的开发工作流程。关键概念是确保移动到 master 的代码是经过审查和测试的。

  • 编写提交消息: 提交消息是存储库中所有活动的日志。花时间写一个有用的更改描述:

    • 代码审阅者可以查看您的提交以了解您的工作流程和所做的更改。
    • 如果您想要回滚更改,能一眼找到所需提交会容易得多。
    • 请注意,点击构建按钮将自动生成带有时间戳的提交消息;避免使用此功能。先点击提交,编写提交消息,然后点击构建。
  • 修剪分支: 在长期存在的存储库中,分支可能会积累。如果分支的开发被放弃,特别是如果一个分支被合并到另一个中,通过删除分支保持整洁。这有助于了解哪些分支正在积极开发。

  • 升级存储库: 当提示时,按照步骤升级存储库中的语言包。此过程将打开一个到包含升级的活动分支的拉取请求。您应随时在升级分支上运行管道的构建,以确保没有版本提升影响您的代码。然而,保持这些升级的最新状态通常可以确保您不会遇到其他地方看到的边缘情况,这些情况在升级版本中已修补。

  • 实践代码审查: 当您与队友合作开发变换时,在拉取请求过程中实施一些代码审查实践。我们分享了关于代码审查最佳实践 ↗ 的想法,许多概念同样适用于审查数据变换代码。

模式

在存储库之间共享代码: Foundry 中的存储库在项目级别操作出于多种原因,但通常有逻辑可以跨管道重用,这有几个优点:

  • 根据 DRY 的通用代码重用。

  • 避免在数据基础的不同区域之间分叉/不一致的逻辑。

  • 可能有管道理想情况下使用基础管道但具有更严格的 SLA 或性能要求。在这种情况下,解决方案通常是共享逻辑而不是变换/数据集,以便关键管道可以依赖预筛选数据集、更少的变换、具有不同的构建计划等。

  • 代码存储库是实现这一目标的绝佳方式。例如,在使用 Python 变换时,库可以将自己发布到 Conda 渠道并允许其他存储库使用它们。请参阅有关共享 Python 库的文档。

  • 共享存储库的一个额外优势是语义版本控制。共享存储库可以用版本标记其提交(例如 1.0.0、1.0.1、2.0.0),使用库可以选择如何获取新版本。例如,存储库可能选择获取最新版本(上述 2.0.0)或仅获取特定版本(例如 1.0.1),并推迟获取新版本,直到他们手动决定这样做。后一种情况在管道至关重要且管道所有者希望有机会选择加入/批准对共享存储库的更改时特别有价值。

  • 发布: 同样地,如果团队希望为管道提供明确的发布计划,一种选择(避免使用暂存实例或长期存在的开发分支)是将逻辑提取到共享存储库中的函数中,并使用语义版本控制将使用存储库保持在主要版本,如 1.0.0、2.0.0,甚至 0.0.0。这样,开发者可以继续迭代逻辑并标记中间版本,而无需在 master 上上线。此外,在使用存储库的分支上,开发者始终可以选择中间版本,只要他们不在发布日期之前将它们合并到 master 即可。

单元测试

单元测试是一种流行的提高和维护代码质量的方法。在单元测试 ↗中,软件的小而离散的组件(“单元”)以独立、独立和自动化的方式进行测试。

  • Python 单元测试: 您可以通过遵循 Python 单元测试说明 在 Python 变换存储库的 CI 检查中启用 pytest 单元测试。

  • Java 单元测试: Java 变换单元测试的配置步骤可以在 Java 单元测试文档中找到。

最佳实践

单元测试应:

  • 每次只测试一个逻辑;
  • 轻量级(请参阅特定语言的说明以获取如何减少内存使用的提示);
  • 不依赖于额外输入来运行;并且
  • 不依赖或调用彼此。

健康检查

最佳实践

检查您的数据健康状况: 通常,一旦管道的一部分完成,很容易设置计划并将其置之不理。然而,即使您的逻辑是可靠的,传入的数据也可能会以影响构建的方式改变,导致性能下降、数据规模增加或直接构建失败。配置基本的数据集大小和构建时间检查,即使您没有配置警报,也会提供这些关键指标随时间变化的视图,因此您可以观察到,例如数据集大小的增长率或数据集的平均构建时间。阅读有关可用的具体健康检查的信息以及如何配置它们。

模式

扩展健康检查: 在大多数情况下,默认的健康检查配置应该足够。如果您需要进一步的灵活性,考虑在管道中添加一个或多个派生的 健康检查 数据集。此数据集的变换可以执行任意逻辑以确定输入数据集(您正在验证的数据集)的有效性,然后输出格式化的数据,以便简单的健康检查,如 允许值,可以报告数据集是否有效。

为该数据集设置一个计划,以便在输入数据集更新时进行构建,您将拥有一组额外的全面健康检查。

调度

最佳实践

查看调度最佳实践