[英]Best practice for Gradle api vs implementation in multi-module project
This is not the commonly asked question about the difference between api
and implementation
, and hopefully will be more advance and interesting from the point of view of architecting a multi-module project. 这不是关于
api
和implementation
之间差异的常见问题,并且从架构多模块项目的角度来看,希望会更加先进和有趣。
Let say I have the following modules in an application 假设我在应用程序中有以下模块
library
base
feature1
feature2
app
Now the relations between the modules are: 现在模块之间的关系是:
base
wraps library
base
包装library
feature1
and feature2
make use (depends) on base
feature1
和feature2
在base
上使用(取决于)
app
puts together feature1
and feature2
app
将feature1
和feature2
放在一起
Everything in this multi module structure should be able to work using Gradle's implementation
dependencies and there's no need to use the api
clause anywhere. 这个多模块结构中的所有内容都应该能够使用Gradle的
implementation
依赖项工作,并且不需要在任何地方使用api
子句。
Now, let say feature1
needs to access an implementation detail of base
included in library
. 现在,假设
feature1
需要访问base
包含的library
的实现细节。
In order to make library
available to feature1
we have two options as far as I can tell: 为了使
library
可用于feature1
,据我feature1
我们有两个选项:
Change implementation
for api
in base
to leak the dependency to modules that depend on base
更改
implementation
的api
的base
泄漏依赖于依赖于模块base
Add library
as an implementation
dependency to feature1
without having base
leak the dependency on library
将
library
作为implementation
依赖项添加到feature1
而没有base
泄漏对library
的依赖性
Of course the example has been simplified for the sake of the question, but you understand how this can became a configuration hell with a big number of modules with 4 or 5 levels of dependencies. 当然,为了解决这个问题,这个例子已经被简化了,但是你明白了这可能会成为一个具有4或5级依赖关系的大量模块的配置地狱。
We could create a base-feature
intermediate module that can wrap base
and provide another level of abstraction for feature1
to consume without leaking library
, but let's leave that solution out of the scope of this problem to focus on the setup of the dependencies. 我们可以创建一个
base-feature
中间模块,它可以包装base
并为feature1
提供另一个抽象级别,而不会泄漏library
,但让我们将该解决方案排除在此问题的范围之外,以专注于依赖项的设置。
Some trade-offs that I detected on the above options: 我在上述选项中检测到的一些权衡 :
Option 1) pros 选项1)专业人士
build.gradle
's files, as no need to repeat implementation
clauses build.gradle
文件,因为不需要重复implementation
子句 api
clause and see the changes propagated to all consumer modules api
子句进行单一更改,并查看传播到所有使用者模块的更改 Option 1) cons 选项1)缺点
Option 2) pros 选项2)专业人士
Option 2) cons 选项2)缺点
implementation
clause have to be modified. implementation
子句的所有模块。 Even though I believe this is a good thing because it keeps track exactly of how a change modified the project, I see how it can take more time. Now the questions : 现在的问题是 :
Is there any trade-offs in terms of compilation of this multi-module scenario? 在编译这个多模块场景时是否有任何权衡取舍?
Is a module leaking a dependency "faster" to be compiled for consumer modules? 是否一个模块泄漏依赖“更快”以便为消费者模块编译?
Does it make a substantial difference in build times? 它是否会在构建时间上产生重大影响?
What other side effects, pros/cons am I missing ? 我还缺少哪些其他副作用,利弊?
Thanks for your time. 谢谢你的时间。
Reposting from the Gradle forum thread. 从Gradle论坛帖子中重新发布。
What you describe is a fairly common discussion about layered architecture systems, also known as "strict" vs "loose" layering, or "open" vs "closed" layers. 您所描述的是关于分层架构系统的相当常见的讨论,也称为“严格”与“松散”分层,或“开放”与“封闭”层。 See this (hopefully free for you too) chapter from Software Architecture Patterns for some semiotics which is unlikely to help you much with your choice
从软件架构模式中可以看到这个(希望对你来说也是免费的)一些符号学的章节,它不太可能对你的选择有所帮助
From my point of view, if a module needs to break layering, I'd model the project structure to expose this in the most direct and visible way. 从我的角度来看,如果一个模块需要打破分层,我会对项目结构进行建模,以最直接和最明显的方式公开它。 In this case it means adding
library
as implementation dependency of feature1
. 在这种情况下,它意味着添加
library
作为feature1
实现依赖性。 Yes it makes the diagram uglier, yes it forces you to touch few more files on upgrade, and that is the point - your design has a flaw and it is now visible. 是的,这使得图表更加丑陋,是的,它迫使您在升级时触摸更多文件,这就是重点 - 您的设计存在缺陷,现在可见。
If few modules need to break the layer encapsulation in the same way, I may consider adding a separate base module exposing that functionality, with a name such as base-xyz
. 如果很少有模块需要以相同的方式打破层封装,我可以考虑添加一个单独的基本模块,使用诸如
base-xyz
类的名称来公开该功能。 Adding a new module is a big thing, not because of the technical work, but because our brain can handle only so many "things" at a time (chunking). 添加新模块是一件大事,不是因为技术工作,而是因为我们的大脑一次只能处理这么多“事物”(分块)。 I believe the same would hold for Gradle "variants" when they become available, but I can't claim that yet as I haven't tried them hands on.
我相信当Gradle“变种”可用时,它们会同样适用,但我还没有声称,因为我没有尝试过它们。
If all clients of the base
module need to access library
(ie because you use classes or exceptions from library
in your public signatures) then you should expose library
as API dependency of base
. 如果所有的客户
base
模块需要访问library
(即因为你使用类或例外,从library
在公开的签名),那么你应该揭露library
作为API的依赖base
。 The downside of that is that library
becomes part of the public API of base
, and it is probably bigger than you would like, and not under your control. 其缺点是
library
成为base
公共API的一部分,它可能比您想要的更大,而且不在您的控制之下。 Public API is something you are responsible for, and you want to keep it small, documented, and backwards compatible. 公共API是您负责的事情,并且您希望将其保持小,记录和向后兼容。
At this point you may be thinking about jigsaw modules (good), osgi (err... don't), or wrapping the parts of lib that you need to expose in your own classes (maybe?) 此时你可能正在考虑拼图模块(好),osgi(错误...不要),或者包装你需要在自己的类中暴露的lib部分(也许?)
Wrapping only for the sake of breaking dependencies is not always a great idea. 仅仅为了破坏依赖性而包装并不总是一个好主意。 For one it increases the amount of code you maintain and (hopefully) document.
例如,它增加了您维护和(希望)文档的代码量。 If you start doing small adaptations in the
base
layer, and the library
is a well known library, you introduce (value added) inconsistencies - one needs to always be on guard whether their assumptions for lib still hold. 如果您开始在
base
层中进行小的修改,并且library
是一个众所周知的库,那么您会引入(增值)不一致性 - 需要始终保持他们对lib的假设是否仍然成立。 Finally, often the thin wrappers end up leaking the library design, so even if they wrap the API - that still forces you to touch the client code when you replace/upgrade lib, at which point you may have been better off using lib directly. 最后,瘦包装器通常会泄漏库设计,所以即使它们包装了API - 当你更换/升级lib时仍然会强迫你触摸客户端代码,此时你可能最好直接使用lib。
So, as you can see, is about trade-offs and usability. 因此,正如您所看到的,是关于权衡和可用性。 The CPU doesn't care where your module boundaries lie, and all developers are different - some cope better with large amount of simple things, some cope better with small number of highly abstract concepts.
CPU并不关心模块边界所在的位置,并且所有开发人员都是不同的 - 有些人会更好地处理大量简单的事情,有些人会更好地处理少量高度抽象的概念。
Don't obsess about the best (as in What Would Uncle Bob Do) design when any good design would work. 当任何好的设计都能奏效时,不要痴迷于最好的(就像在鲍勃叔叔那里做的那样)。 The amount of extra complexity that is justified for the sake of introducing order is a fuzzy quantity, and is something that you are in charge of deciding.
为了引入顺序而合理的额外复杂性的数量是模糊量,并且是您负责决定的事情。 Make you best call and don't be afraid to change it tomorrow :-)
让你最好的电话,不要害怕明天改变它:-)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.