Gradle api 与多模块项目中实施的最佳实践
Best practice for Gradle api vs implementation in multi-module project
这不是关于 api
和 implementation
之间区别的常见问题,希望从构建多模块项目的角度来看会更先进、更有趣。
假设我在应用程序中有以下模块
library
base
feature1
feature2
app
现在模块之间的关系是:
base
换行 library
feature1
和 feature2
使用(取决于)base
app
放在一起 feature1
和 feature2
这个多模块结构中的所有内容都应该能够使用 Gradle 的 implementation
依赖项工作,并且不需要在任何地方使用 api
子句。
现在,假设 feature1
需要访问 library
中包含的 base
的实现细节。
为了让 library
对 feature1
可用,据我所知,我们有两个选择:
为 base
中的 api
更改 implementation
以将依赖性泄露给依赖于 base
[=128 的模块=]
将 library
作为 implementation
依赖添加到 feature1
而不会 base
泄漏对 library
[=51= 的依赖]
当然,为了问题的缘故,这个例子已经被简化了,但是你明白这如何成为一个配置地狱,其中有大量模块,具有 4 或 5 级依赖关系。
我们可以创建一个 base-feature
中间模块,它可以包装 base
并为 feature1
提供另一个抽象级别以在不泄漏 library
的情况下使用,但让我们保留它超出此问题范围的解决方案将重点放在依赖项的设置上。
我在上述选项中发现的一些权衡:
选项 1) 优点
- 较小的
build.gradle
文件,因为不需要重复 implementation
个子句
- 更快的构建脚本编辑。只需对
api
子句进行单一更改,然后查看传播到所有消费者模块的更改
选项 1) 缺点
- 类 可能会出现在不应访问它们的模块中。
- 开发人员容易错过使用,因为他们有可用的实现,而不仅仅是模块接口。
选项 2) 优点
- 它使crystal清楚模块具有哪些依赖项。
- 不用猜测 类 来自哪里(想想 4 或 5 级模块泄漏依赖项),因为它们的来源总是在模块依赖项中声明。
选项 2) 缺点
- 使更新依赖更加乏味,因为必须修改所有带有
implementation
子句的模块。尽管我认为这是一件好事,因为它可以准确跟踪更改是如何修改项目的,但我知道它会花费更多时间。
现在问题:
这个多模块场景在编译方面有没有取舍?
模块是否泄漏了要为消费者模块编译的依赖项"faster"?
它对构建时间有显着影响吗?
还有什么其他副作用,pros/cons我错过了吗?
感谢您的宝贵时间。
从 Gradle forum 主题重新发布。
您描述的是关于分层架构系统的相当普遍的讨论,也称为 "strict" 与 "loose" 分层,或 "open" 与 "closed" 层。请参阅 Software Architecture Patterns 中的这一章(希望对您也免费)了解一些符号学,这不太可能对您的选择有很大帮助
从我的角度来看,如果一个模块需要打破分层,我会为项目结构建模,以最直接和可见的方式公开它。在这种情况下,它意味着添加 library
作为 feature1
的实现依赖。是的,它使图表更丑陋,是的,它迫使您在升级时接触更多文件,这就是重点 - 您的设计存在缺陷,现在可见。
如果很少有模块需要以相同的方式打破层封装,我可能会考虑添加一个单独的基础模块来公开该功能,名称如 base-xyz
。添加一个新模块是一件大事,不是因为技术工作,而是因为我们的大脑一次只能处理这么多 "things"(分块)。我相信 Gradle "variants" 可用时也是如此,但我还不能断言,因为我还没有亲自尝试过它们。
如果 base
模块的所有客户端都需要访问 library
(即因为您在 public 签名中使用 类 或来自 library
的例外) 那么您应该将 library
公开为 base
的 API 依赖项。缺点是 library
成为 base
的 public API 的一部分,并且它可能比您想要的要大,并且不受您的控制。 Public API 是您负责的事情,您希望它保持小巧、有据可查且向后兼容。
在这一点上,您可能正在考虑拼图模块(很好)、osgi(错误...不要),或者包装您需要在自己的 类 中公开的部分库(也许吧?)
仅仅为了打破依赖而包装并不总是一个好主意。一方面,它增加了您维护和(希望)记录的代码量。如果你开始在 base
层做一些小的改编,而 library
是一个众所周知的库,你就会引入(增值)不一致——人们需要时刻警惕他们对 lib 的假设是否仍然成立.最后,瘦包装器通常最终会泄漏库设计,因此即使它们包装了 API - 这仍然会迫使您在 replace/upgrade lib 时接触客户端代码,此时您可能已经最好直接使用 lib。
因此,如您所见,是关于权衡取舍和可用性。 CPU 不关心你的模块边界在哪里,所有的开发人员都是不同的——有些人能更好地处理大量简单的事情,有些人能更好地处理少量高度抽象的概念。
当任何好的设计都可行时,不要沉迷于最好的(如 Bob 叔叔会做什么)设计。为了引入秩序而证明的额外复杂性是一个模糊的数量,并且是你负责决定的。给你最好的电话,不要害怕明天改变它:-)
这不是关于 api
和 implementation
之间区别的常见问题,希望从构建多模块项目的角度来看会更先进、更有趣。
假设我在应用程序中有以下模块
library
base
feature1
feature2
app
现在模块之间的关系是:
base
换行 library
feature1
和 feature2
使用(取决于)base
app
放在一起 feature1
和 feature2
这个多模块结构中的所有内容都应该能够使用 Gradle 的 implementation
依赖项工作,并且不需要在任何地方使用 api
子句。
现在,假设 feature1
需要访问 library
中包含的 base
的实现细节。
为了让 library
对 feature1
可用,据我所知,我们有两个选择:
为
[=128 的模块=]base
中的api
更改implementation
以将依赖性泄露给依赖于base
将
library
作为implementation
依赖添加到feature1
而不会base
泄漏对library
[=51= 的依赖]
当然,为了问题的缘故,这个例子已经被简化了,但是你明白这如何成为一个配置地狱,其中有大量模块,具有 4 或 5 级依赖关系。
我们可以创建一个 base-feature
中间模块,它可以包装 base
并为 feature1
提供另一个抽象级别以在不泄漏 library
的情况下使用,但让我们保留它超出此问题范围的解决方案将重点放在依赖项的设置上。
我在上述选项中发现的一些权衡:
选项 1) 优点
- 较小的
build.gradle
文件,因为不需要重复implementation
个子句 - 更快的构建脚本编辑。只需对
api
子句进行单一更改,然后查看传播到所有消费者模块的更改
选项 1) 缺点
- 类 可能会出现在不应访问它们的模块中。
- 开发人员容易错过使用,因为他们有可用的实现,而不仅仅是模块接口。
选项 2) 优点
- 它使crystal清楚模块具有哪些依赖项。
- 不用猜测 类 来自哪里(想想 4 或 5 级模块泄漏依赖项),因为它们的来源总是在模块依赖项中声明。
选项 2) 缺点
- 使更新依赖更加乏味,因为必须修改所有带有
implementation
子句的模块。尽管我认为这是一件好事,因为它可以准确跟踪更改是如何修改项目的,但我知道它会花费更多时间。
现在问题:
这个多模块场景在编译方面有没有取舍?
模块是否泄漏了要为消费者模块编译的依赖项"faster"?
它对构建时间有显着影响吗?
还有什么其他副作用,pros/cons我错过了吗?
感谢您的宝贵时间。
从 Gradle forum 主题重新发布。
您描述的是关于分层架构系统的相当普遍的讨论,也称为 "strict" 与 "loose" 分层,或 "open" 与 "closed" 层。请参阅 Software Architecture Patterns 中的这一章(希望对您也免费)了解一些符号学,这不太可能对您的选择有很大帮助
从我的角度来看,如果一个模块需要打破分层,我会为项目结构建模,以最直接和可见的方式公开它。在这种情况下,它意味着添加 library
作为 feature1
的实现依赖。是的,它使图表更丑陋,是的,它迫使您在升级时接触更多文件,这就是重点 - 您的设计存在缺陷,现在可见。
如果很少有模块需要以相同的方式打破层封装,我可能会考虑添加一个单独的基础模块来公开该功能,名称如 base-xyz
。添加一个新模块是一件大事,不是因为技术工作,而是因为我们的大脑一次只能处理这么多 "things"(分块)。我相信 Gradle "variants" 可用时也是如此,但我还不能断言,因为我还没有亲自尝试过它们。
如果 base
模块的所有客户端都需要访问 library
(即因为您在 public 签名中使用 类 或来自 library
的例外) 那么您应该将 library
公开为 base
的 API 依赖项。缺点是 library
成为 base
的 public API 的一部分,并且它可能比您想要的要大,并且不受您的控制。 Public API 是您负责的事情,您希望它保持小巧、有据可查且向后兼容。
在这一点上,您可能正在考虑拼图模块(很好)、osgi(错误...不要),或者包装您需要在自己的 类 中公开的部分库(也许吧?)
仅仅为了打破依赖而包装并不总是一个好主意。一方面,它增加了您维护和(希望)记录的代码量。如果你开始在 base
层做一些小的改编,而 library
是一个众所周知的库,你就会引入(增值)不一致——人们需要时刻警惕他们对 lib 的假设是否仍然成立.最后,瘦包装器通常最终会泄漏库设计,因此即使它们包装了 API - 这仍然会迫使您在 replace/upgrade lib 时接触客户端代码,此时您可能已经最好直接使用 lib。
因此,如您所见,是关于权衡取舍和可用性。 CPU 不关心你的模块边界在哪里,所有的开发人员都是不同的——有些人能更好地处理大量简单的事情,有些人能更好地处理少量高度抽象的概念。
当任何好的设计都可行时,不要沉迷于最好的(如 Bob 叔叔会做什么)设计。为了引入秩序而证明的额外复杂性是一个模糊的数量,并且是你负责决定的。给你最好的电话,不要害怕明天改变它:-)