前文讨论了敏捷数据工程实践的相关概念。有哪些具体的敏捷数据工程实践呢?本文将分享“代码化一切”的实践。
代码化XX
在应用软件开发中,“代码化一切”被讨论得很多。常见的代码化XX有:
- 配置即代码(Configuration as code):将配置文件放入代码仓库进行管理,可以实现配置修改的可追溯性。
- 基础设施即代码(Infrastructure as code):将基础设施需要的通用能力抽象出来,以便可以用代码来定义基础设施。然后将基础设施代码放入代码仓库进行管理,可实现随时可重建的基础设施。通常借助一些工具实现,比如
Kubernetes
支持用yaml
文件代码来定义基于容器的基础设施,Terraform
支持用yaml
文件代码定义各类基础设施,并通过插件来支持几乎所有的基础设施提供商(如本地服务器、AWS/GCP/Azure云服务等)。 - 流水线即代码(Pipeline as code):避免通过持续集成工具(如Jenkins)的
UI
上的复杂配置来定义流水线,而是通过代码来定义持续集成流水线。早在2016年就在Thoughtworks的技术雷达上提出,后来得到了各种主流的持续集成工具的支持。比如Jenkins支持用Groovy代码来定义流水线,GitHub Actions/GitLab/Circle CI/Travis CI等支持用yaml
代码来定义流水线。
除了这些,还有更多的“代码化XX”实践,比如:
代码化的优势
从上面这些“代码化XX”中,我们可以看到一个趋势,似乎我们正在尝试把“一切”代码化。为什么“代码化”这么有吸引力?
这要追溯到开发人员日常工作方式中。作为一个程序员,开发人员每天做得最多的事情是写代码,最习惯最熟练的工作也是写代码。通过一个熟悉的集成开发环境(IDE)或者文本编辑器,开发人员可以高效的编写、调试代码并完成工作。
正由于此,现在市场上有大量成熟的帮助开发人员写代码的工具。它们大都支持了数量众多的快捷键,可辅助编写代码的智能代码提示,语法检查等等对代码编写非常友好的功能。开发人员还往往会根据自己的习惯对这些工具进行配置,以便达到最高的编码效率。
不难看出,正是由于这些工作方式,所以开发人员会更希望以代码化的方式来工作,这也就推动了“代码化一切”这样的工程思想的发展。
除了可以高效编辑之外,代码化之后还能获得这样一些好处:
- 可追踪变更历史记录:开发人员有成熟的代码版本控制工具可用于记录每一次修改内容、修改人、修改时间、注释等。如果有必要,还可以比较任意两个版本的差异。对于诊断问题而言,这无疑是非常高效的。
- 可回退到任一版本:由于待开发的功能往往非常逻辑复杂,因此,常常会隐藏一些问题在交付的软件中。如果实现了“代码化”,则可根据需要随时回退到某一个无问题的版本。
- 可融入开发人员的日常开发实践:代码化之后,可以更容易的融入到开发团队的日常实践中,比如代码评审Code Review
非代码化工作方式
如果没有“代码化”,情况会怎样呢?“代码化”的反面常常体现为:
- 开发人员通过一个可视化程序设计器辅助进行开发,通常通过拖拽完成
- 开发人员通过一个复杂的可视化配置界面,通过提供大量配置来完成开发
此类工具通常给人一种低门槛高效率进行软件开发的错觉,但是深入使用之后,开发人员往往会觉得开发过程很痛苦且效率也很低。
在用户界面应用程序开发中,可视化程序设计器被广泛用于所见即所得的界面开发。这一开发方式看上去很美好,但是一旦应用程序逻辑比较复杂之后,常常由于难以调试,无法支持重构且修改成本特别高而降低开发效率。业界将这一反模式称为Smart UI。提供这一开发方式的程序设计器,一般也不会提供代码,开发人员自然也无法享受到代码化之后获得的版本控制能力,难以进行代码评审、历史回溯、版本回滚等。
通过提供一个复杂配置界面来辅助开发是另一种非代码化的形式。这种方式常常将原本通过简单的代码可以实现的功能,复杂化为一个长长的配置。开发人员不得不去理解每一个配置的意义,配置间的逻辑关系,然后配置一个特别长的且无法纳入版本控制的配置。
当然,此类工具也有其价值,对于简单场景的开发工作,它们确实可以帮助开发人员快速完成开发。
“非代码化”工具的代码化支持
虽然“非代码化”不是一个好的实践,但是市面上很多此类工具往代码化的方向多走了一步,从而使得这一情况得到改善。比如,很多开发工具提供了代码导出的功能,使得开发人员具备了基于代码库进行程序管理的能力(如ant design landing)。另一些工具可以直接连接到代码库,所有的修改都可以保存到代码库中(如Databricks)。还有一些工具,虽然没有提供导出代码的功能,但是却提供了完善的API来获取或者更新配置,开发人员可以利用这样的API间接实现代码化的工作方式(如AWS提供了完善的API来配置各类资源)。
有代码化能力的程序设计器是一种推荐的开发工具。它不仅可以提供低门槛,同时也兼具了代码化的优势。如果此类工具可以提供一些方式使得开发人员可以进行自定义的抽象和封装,那就更好了。
数据开发中的“代码化”
数据开发中,我们一般要面对这样几类开发资源:基础设施、安全配置、ETL代码、ETL任务配置、数据流水线、运维脚本、业务注释等。
事实上,这些资源均可以很容易的被“代码化”。
基础设施可以通过Terraform进行代码化。如果整个系统运行在类似Kubernetes这样的容器平台上,也可以Kubernetes提供的YAML来定义基础设施。
安全配置代码化常常需要一定的开发成本,一般可借助于各类安全管理应用提供的API进行代码化。一个推荐的做法如下。首先根据具体的应用场景设计安全管理模型,并据此模型定义(较少的)配置项,然后提供一个程序读取这些配置,并根据安全管理模型生成安全管理工具提供的API所对应的数据,最后调用安全管理工具提供的API完成安全配置的应用。
ETL一般以代码的形式存在,大部分的数据开发工具都提供了功能,使得开发者可以用SQL的来开发ETL。但是只有SQL常常难以满足开发需求,比如,我们很难在SQL中发送HTTP请求、打印日志或断点调试。这里可以推荐Thoughtworks开源的Easy SQL,它基于SQL进行语法增强,提供了一种方式使得我们可以更加模块化的组织ETL代码,支持了变量、日志打印、断言、调试、外部函数调用等等功能。有了这些功能,我们可以在ETL代码中完成各式各样的工作,无需再结合其他工具使用,也无需自己编写复杂的代码。
ETL任务配置是指ETL任务运行时使用的各类配置。很多数据计算引擎都提供了配置接口,以便我们可以根据需要来配置最适当的计算资源、配置程序运行所需的外部文件、配置算法等。这些配置看起来不起眼,但是也非常重要,因为它们常常可以决定程序运行时性能,而这跟ETL任务的运行时间、稳定性紧密相关。所以,将ETL配置纳入到代码库中管理就显得十分必要。Easy SQL提供了一种能力,使得开发人员可以在ETL文件中定义ETL执行所需的配置,是一种支持将配置与对应的代码放在一起的好的实践。
数据流水线常常以一种“非代码化”的方式进行开发。很多调度工具都提供了界面,使得我们可以通过拖拽及配置来完成流水线的开发。比如阿里的Dataworks,Azure的ADF等。以“非代码化”的方式开发流水线的灵活性很差且无法享受到版本控制的优势。一些开源工具提供了代码化能力,比如受到很多数据开发人员喜爱的Airflow,它支持用python代码来定义数据流水线,然后根据流水线定义进行可视化展示。对开发人员更友好的方式是,提供一种自动管理数据流水线的机制,这样开发人员就无需编写流水线了。这是可能的,事实上,完全可以编写一个程序,解析出ETL代码中的输入输出表,然后根据这些信息自动提取ETL间的依赖关系,接着根据这些依赖关系就可以自动生成数据流水线了。
运维脚本常常以代码的形式呈现,但是很多数据工具希望将此类脚本纳入工具内部管理。这容易让我们丧失代码化的能力,因为它总是鼓励我们将此类代码配置到工具的UI界面里(可以想象一下在Jenkins还不支持用Groovy编写CI/CD流水线时的使用方式)。
业务注释是另一类可以考虑代码化的资源。很多团队将此类信息纳入到一个名为元数据管理的应用中进行管理。元数据管理应用通常可以提供一些基于自然语言的搜索能力,而且可以提供友好的界面展示。这是其优势,但是对于此类信息的维护,就不得不在元数据管理应用中完成。这常常带来另一些问题。比如,当我们重建某些数据库表时,元数据管理应用无法将原来的元数据迁移到新表。还比如,元数据管理应用常常无法提供完善的数据版本管理功能,从而使得我们无法进行版本追溯及回滚。如果将此类业务注释放到代码库中进行管理,就可以享受到代码化的优势,并且,通过调用元数据管理应用的API可以此元数据同步到元数据管理应用,从而我们也能享受到元数据管理应用提供的搜索即友好的数据展示的能力。
当然,实际项目中可能还有其他一些没有提到的资源类型,这里不在于为所有资源列举代码化方案,而是更多的提供一种代码化一切的建议。当我们发现团队正在以一种非代码化的方式进行数据开发时,可能需要思考有没有什么好的方案可以转变为代码化的方式。这将给我们的开发带来非常多的好处。
总结
本文讨论了数据开发中的一个重要敏捷实践–代码化一切。结合敏捷软件开发过程,我们分析了代码化一切的实践所带来的好处,并分析了与代码化相反的做法的缺点。最后,结合数据开发的实际情况,将数据开发过程中涉及的各类资源进行了分类,并为每一类资源提供了代码化的建议。