You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
(Uber employees may reference the original doc: here)
An issue can occur when using ScopeFactory to connect a parent and child scope to one another.
Dependencies listed in a hand-made Motif dependencies interface could match up with differently annotated access methods in the parent scope, causing, for example, application contexts to be provided where activity contexts were expected. A minimal example repository is available for inspection here, but the critical area is shown below:
@motif.ScopeinterfaceScopeParent: ScopeCreatableChild.Dependencies {
funscopeCreatableChild(): ScopeCreatableChild
@AppContext
funcontext(): Context
@ActivityContext
funactivityContext(): Context
}
@motif.ScopeinterfaceScopeCreatableChild: Creatable<ScopeCreatableChild.Dependencies> {
@ActivityContext
funactivityContext(): ContextinterfaceDependencies {
// Matches to the activity method if created by a Motif child method,// but matches to the application method if created by [ScopeBuilder.create]
@ActivityContext funcontext(): Context
}
}
funmain() {
val scopeParent =ScopeFactory.create(ScopeParent::class.java)
// prints “Parent: Activity”println("Parent: ${scopeParent.activityContext().type}")
// prints “ScopeCreateableChild[Child Method]: Activity”println("ScopeCreateableChild[Child Method]: ${scopeParent.scopeCreatableChild().activityContext().type}")
val scopeManualChild =ScopeFactory.create(ScopeCreatableChild::class.java, scopeParent)
// prints “ScopeCreateableChild[Child Method]: Application”println("ScopeCreateableChild[ScopeFactory]: ${scopeManualChild.activityContext().type}")
}
Two solutions have been put forward internally:
Perspective 1
Author: @wynneplaga
Summary: Use of Creatable with ScopeFactory, except for root scopes, is an anti-pattern and should be forbidden
The Motif README explicitly discusses the use of ScopeFactory within the context of root scopes. This makes sense– Motif has a separate system for tying together parent and child scopes: the child scope method. As a result, using the ScopeFactory in order to recreate the parent-child relationship should be regarded as an anti-pattern, because it breaks Motif’s ability to match up dependencies and sources correctly. It also defeats Motif’s ability to detect duplicate sources when a parent already exposes the needed source. Rather than circumventing child methods by using a different pattern, any shortcomings with the behavior of child scope methods should be addressed by updating Motif itself.
Some standards recommend to use the ScopeFactory as an alternative to child scopes when creating impl feature modules, but the justification offered (“if a feature wants to declare exactly what dependencies should come from its parent”) can be adequately addressed by using Creatable with a traditional child method. This would allow developers to avoid reliance on generated code if they would like, but does not impair Motif’s ability to reason about sources and dependencies.
Suggested solution: A note discouraging the use of ScopeFactory outside of tests should be added, with a note that it is permissible to use for a truly root scope. Existing uses of ScopeFactory out of compliance with this rule should be eliminated. Users should be encouraged to add lint rules that flag uses of ScopeFactory
Perspective 2
Author: @SergeySmykovskyi
Summary: Modify the Motif’s codegen logic to detect and restrict overlap of methods signatures, to keep ScopeFactory usages in reasonable places.
The root cause of the issue is inheritance from the interface, which ignores the @Qualifier or @Named annotation used for DI code generation and uses @Override-equivalent of an already defined function in the Parent scope. That means that not only ScopeFactory usages are affected but also all of the PluginFactorories with Parent interface injection where the Scope inherits the Parent interface.
As was mentioned in the Perspective 1 the Motif documentation explicitly saying about usage of ScopeFactory for RootScope creation but at the same time doesn’t have examples of architecture in multi-modular projects, so “root” scope can be recognized as the root one of specific feature/library module. Restricting usage of the ScopeFactory will require us to keep all of the Scopes as a public API with doesn’t match “strict visibility control” principle and as a result will expose implementation details to external modules.
Suggested solution:
(preferred) Detect the interface inheritance that is used as a dependency for Creatable<*> and enforce using the same @Qualifier/@Named annotations as in Parent-matched provider
Pros
As part of integration's new lib version, all existing issues will be detected and resolved
(Uber employees may reference the original doc: here)
An issue can occur when using ScopeFactory to connect a parent and child scope to one another.
Dependencies listed in a hand-made Motif dependencies interface could match up with differently annotated access methods in the parent scope, causing, for example, application contexts to be provided where activity contexts were expected. A minimal example repository is available for inspection here, but the critical area is shown below:
Two solutions have been put forward internally:
Perspective 1
Author: @wynneplaga
Summary: Use of
Creatable
withScopeFactory
, except for root scopes, is an anti-pattern and should be forbiddenThe Motif README explicitly discusses the use of
ScopeFactory
within the context of root scopes. This makes sense– Motif has a separate system for tying together parent and child scopes: the child scope method. As a result, using theScopeFactory
in order to recreate the parent-child relationship should be regarded as an anti-pattern, because it breaks Motif’s ability to match up dependencies and sources correctly. It also defeats Motif’s ability to detect duplicate sources when a parent already exposes the needed source. Rather than circumventing child methods by using a different pattern, any shortcomings with the behavior of child scope methods should be addressed by updating Motif itself.Some standards recommend to use the
ScopeFactory
as an alternative to child scopes when creating impl feature modules, but the justification offered (“if a feature wants to declare exactly what dependencies should come from its parent”) can be adequately addressed by usingCreatable
with a traditional child method. This would allow developers to avoid reliance on generated code if they would like, but does not impair Motif’s ability to reason about sources and dependencies.Suggested solution: A note discouraging the use of
ScopeFactory
outside of tests should be added, with a note that it is permissible to use for a truly root scope. Existing uses ofScopeFactory
out of compliance with this rule should be eliminated. Users should be encouraged to add lint rules that flag uses ofScopeFactory
Perspective 2
Author: @SergeySmykovskyi
Summary: Modify the Motif’s codegen logic to detect and restrict overlap of methods signatures, to keep
ScopeFactory
usages in reasonable places.The root cause of the issue is inheritance from the interface, which ignores the
@Qualifier
or@Named
annotation used for DI code generation and uses@Override
-equivalent of an already defined function in the Parent scope. That means that not onlyScopeFactory
usages are affected but also all of the PluginFactorories with Parent interface injection where the Scope inherits the Parent interface.As was mentioned in the Perspective 1 the Motif documentation explicitly saying about usage of
ScopeFactory
for RootScope creation but at the same time doesn’t have examples of architecture in multi-modular projects, so “root” scope can be recognized as the root one of specific feature/library module. Restricting usage of theScopeFactory
will require us to keep all of the Scopes as a public API with doesn’t match “strict visibility control” principle and as a result will expose implementation details to external modules.Suggested solution:
(preferred) Detect the interface inheritance that is used as a dependency for
Creatable<*>
and enforce using the same@Qualifier
/@Named
annotations as in Parent-matched providerCodegen enforcement to keep the method signature the same as generated methods with pattern: (qualifier+generic1+generic2+...+returnType).
The text was updated successfully, but these errors were encountered: