为什么我们需要在 Android 多模块化架构中将 DI 布线与实现分开?
Why do we need to separate DI wiring from Implementation in Android multi modular architecture?
Square Inc. 在 Droidcon SF'19 上展示了它的内部模块化架构:
https://www.droidcon.com/media-detail?video=380843878
但是,我对一些项目符号有点困惑。你能帮帮我吗?
- 为什么他们实际上需要
:wiring
模块?我发现它增加了复杂性:
每个新功能都会获得额外的 gradle 模块
你必须在 :app
的某个地方对你的 Fragments 进行某种全局注入,因为 Fragments 在 :impl
模块中定义无法访问它的 DaggerComponent,它在 :impl-wiring
模块中定义。 :impl
不依赖于 :impl-wiring
,因为依赖是相反的。
你不能有一个 Android 动态功能模块,因为他们应该知道它是 DaggerComponent 以便注入它的 片段。但是无法从 :app
模块进行此类注入,该模块是动态功能的基础模块。
那么为什么要 :wiring
个模块呢?
可以将 :impl
和 :impl-wiring
或 :fake
和 :fake-wiring
合并在一起,以消除上述所有问题。而且,在 :demo-apps
中,可以只依赖于 :impl
或 :fake``, and not on
:impl-wiring(or
:fake-wiring```).
创建此类模块是为了分离更多。有了这个,你就可以生成你使用的组件类型(koin、dagger)以及如何使用的抽象。如果项目很大,那么做是有意义的。
目前我在模块之间生成以下依赖流:
警告:仔细检查方向性。
:feature-A:open <- :feature-A:impl -> :feature-A:impl-wiring
:feature-A:impl-wiring -> :feature-A:impl, :feature-A:open
:app -> :feature-A:open, :feature-A:impl-wiring
我仍然不确定应用是否应该依赖于 open
和 impl-wiring
,或者哪个应用应该只依赖于 [=17] 的 open
和 open
=].
最终,我想到了以下解决方案:
每个功能都包含以下 gradle 个模块:
api
impl
和 fake
data:api
data:impl1
... data:implN
和 data:fake
data:wiring
ui
demo
所以,这里 api
、impl
和 fake
和往常一样,但我将数据层分开了。我自己认为有时我需要多个不同的数据层实现,例如 - 如果我开发 Stock-Charts 应用程序,我可以依赖 Finnhub Open API or MBOUM API 或提供假实现。
因此我有data:api
、data:implX
。事实上,data:api
定义了 FeatureRepository 接口(一个或多个)并且 data:implX
为它们提供了实际的实现。为了绑定接口和实现,我使用 data:wiring
,它定义了 Dagger modules 和 component(s)。此外,我在每个 data:implX
模块中保留相同的包名称,以便 “只写一次” data:wiring
模块。为了用另一个实现替换一个实现,我只更改 data:wiring/build.gradle 中的一行,其中说明了一种:
implementation project(":data:implA")
到
implementation project(":data:implB")
此外,为了打破我原来问题中提到的混淆,我引入了 ui
模块,其中包含一些 Views
的特定功能。 Fragments 进入 demo
(用于测试功能的独立应用程序)或 ui
,它们指的是 viewModel
,其中有一些绑定 ctor-injected from一个特征的 Dagger component。但是 UI 和库在这里是分开的。 Fragment 实例化一个专用的 Dagger component,它使用 component dependencies 来引用功能的库绑定,例如 interactor 或 repository 等
因此,总结一下 - 每个 功能 的 UI 和业务逻辑实现(“库”)之间的分离使得解决问题成为可能。功能的 api
将其功能声明为库的入口点,并且可以通过 :app
的 Dagger multibindings 进行全局访问。所以它可以在任何 :demo
、:ui
和 :dynamic-feature
.
中进一步使用
Square Inc. 在 Droidcon SF'19 上展示了它的内部模块化架构:
https://www.droidcon.com/media-detail?video=380843878
但是,我对一些项目符号有点困惑。你能帮帮我吗?
- 为什么他们实际上需要
:wiring
模块?我发现它增加了复杂性:
每个新功能都会获得额外的 gradle 模块
你必须在
:app
的某个地方对你的 Fragments 进行某种全局注入,因为 Fragments 在:impl
模块中定义无法访问它的 DaggerComponent,它在:impl-wiring
模块中定义。:impl
不依赖于:impl-wiring
,因为依赖是相反的。你不能有一个 Android 动态功能模块,因为他们应该知道它是 DaggerComponent 以便注入它的 片段。但是无法从
:app
模块进行此类注入,该模块是动态功能的基础模块。
那么为什么要 :wiring
个模块呢?
可以将 :impl
和 :impl-wiring
或 :fake
和 :fake-wiring
合并在一起,以消除上述所有问题。而且,在 :demo-apps
中,可以只依赖于 :impl
或 :fake``, and not on
:impl-wiring(or
:fake-wiring```).
创建此类模块是为了分离更多。有了这个,你就可以生成你使用的组件类型(koin、dagger)以及如何使用的抽象。如果项目很大,那么做是有意义的。
目前我在模块之间生成以下依赖流:
警告:仔细检查方向性。
:feature-A:open <- :feature-A:impl -> :feature-A:impl-wiring
:feature-A:impl-wiring -> :feature-A:impl, :feature-A:open
:app -> :feature-A:open, :feature-A:impl-wiring
我仍然不确定应用是否应该依赖于 open
和 impl-wiring
,或者哪个应用应该只依赖于 [=17] 的 open
和 open
=].
最终,我想到了以下解决方案:
每个功能都包含以下 gradle 个模块:
api
impl
和 fake
data:api
data:impl1
... data:implN
和 data:fake
data:wiring
ui
demo
所以,这里 api
、impl
和 fake
和往常一样,但我将数据层分开了。我自己认为有时我需要多个不同的数据层实现,例如 - 如果我开发 Stock-Charts 应用程序,我可以依赖 Finnhub Open API or MBOUM API 或提供假实现。
因此我有data:api
、data:implX
。事实上,data:api
定义了 FeatureRepository 接口(一个或多个)并且 data:implX
为它们提供了实际的实现。为了绑定接口和实现,我使用 data:wiring
,它定义了 Dagger modules 和 component(s)。此外,我在每个 data:implX
模块中保留相同的包名称,以便 “只写一次” data:wiring
模块。为了用另一个实现替换一个实现,我只更改 data:wiring/build.gradle 中的一行,其中说明了一种:
implementation project(":data:implA")
到
implementation project(":data:implB")
此外,为了打破我原来问题中提到的混淆,我引入了 ui
模块,其中包含一些 Views
的特定功能。 Fragments 进入 demo
(用于测试功能的独立应用程序)或 ui
,它们指的是 viewModel
,其中有一些绑定 ctor-injected from一个特征的 Dagger component。但是 UI 和库在这里是分开的。 Fragment 实例化一个专用的 Dagger component,它使用 component dependencies 来引用功能的库绑定,例如 interactor 或 repository 等
因此,总结一下 - 每个 功能 的 UI 和业务逻辑实现(“库”)之间的分离使得解决问题成为可能。功能的 api
将其功能声明为库的入口点,并且可以通过 :app
的 Dagger multibindings 进行全局访问。所以它可以在任何 :demo
、:ui
和 :dynamic-feature
.