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
{{ message }}
This repository has been archived by the owner on Jun 13, 2024. It is now read-only.
One of the benefits of a dependency injection framework is that it provides extra tooling and features for dependency analysis. Cleanse has a few dynamic analysis tools such as cycle detection, component validation, and individual binding validation. These tools operate in the same way by traversing the object graph, catching any errors along the way, and reporting them back to the user.
Although features like cycle detection and component validation make sense to belong inside the core Cleanse framework, building every new feature or tooling that operates over the dependency graph isn't feasible and expands the overall size of the framework (one of the goals of Cleanse is to remain as lightweight as possible). Likewise, there isn't a way for developers to also specify application specific rules about their dependency graph unless they fork the framework and code them in themselves.
Proposed Solution
We propose defining a new public interface that allows developers to create their own validations, features, or tools that hook into the Cleanse object graph. For now, this will be a read-only plugin.
Creating a plugin is done in three steps, first by creating an object that conforms to the protocol CleanseBindingPlugin and implementing the required function:
Inside our GraphvizPlugin implementation, the entry function visit(root:errorReporter) for our plugin is handed a representation of the graph's root component ComponentInfo, and a CleanseErrorReporter. The ComponentInfo instance holds all the information required to traverse over the entire dependency graph, and the CleanseErrorReporter can be used to append errors to report back to the user after validation.
Example Usage
Application specific dependency validation
Consumers of Cleanse may have specific rule sets about their dependency graph for quality or security reasons. One example might be a deprecated class that a developer wants to make sure isn't accidentally included in the dependency graph by his/her other developers.
classMyDeprecatedViewController:UIViewController{}structDeprecatedTypesPlugin:CleanseBindingPlugin{letdeprecatedTypes:[Any.Type]=[MyDeprecatedViewController.self]func visit(root:ComponentBinding, errorReporter:CleanseErrorReporter){
for type in deprecatedType {
if checkComponent(root, for: type){
errorReporter.append(error:Error(type: type))}}}func checkComponent(_ component:ComponentBinding, for type:Any.Type)->Bool{letproviders= component.providers.keys
letfound= providers.contains{ $0.type == type }
if found {return true
}else if component.subcomponents.isEmpty {return false
}else{
for subcomponent in component.subcomponents {
if checkComponent(subcomponent, for: type){return true
}}return false
}}structError:CleanseError{lettype:Any.Typevardescription:String{return"Found deprecated type: \(type)"}}}
Other Use Cases
Graphviz Visualization Tool: A serialized format of the dependency graph could be written to disk and easily mapped to the Graphviz API used to produce a visualization of the Cleanse object graph.
Cleanse CLI Tool: The same serialized format mentioned above could also be used to write a CLI tool that allows writing queries against the object graph. These commands could include
determining if a dependency exist, printing all incoming edges to a dependency, or printing all outgoing edges from a dependency.
Detailed Design
CleanseBindingPlugin
The public service interface is a protocol with one required function.
publicprotocolCleanseBindingPlugin{
/// Plugin entry point function that is called by the service loader.
///
/// - Parameter root: Root component of the object graph.
/// - Parameter errorReporter: Reporter used to append errors that are used to fail validation.
///
func visit(root:ComponentBinding, errorReporter:CleanseErrorReporter)}
This function is the entry point for our plugin and will be called during the validation step of building our Cleanse object graph.
ComponentBinding
The ComponentBinding holds all the necessary details to describe a dependency graph (or subgraph).
/// Describes the object graph for a component node.
publicprotocolComponentBinding{varscope:Scope.Type?{get}varseed:Any.Type{get}varparent:ComponentBinding?{get}varsubcomponents:[ComponentBinding]{get}varcomponentType:Any.Type?{get}
// All bindings registered in the component.
varproviders:[ProviderKey:[ProviderInfo]]{get}}
Internally, the class ComponentInfo will conform to ComponentBinding to provide the implementation. The decision to expose a public protocol instead of raising the access control of ComponentInfo gives us the flexibility in the future to change the underlying implementation. ComponentInfo was implemented alongside the existing dynamic validation features Cleanse provides and is a suitable implementation for ComponentBinding today, but its implementation is likely to change in the future and we'd like to maintain a consistent and stable API for the plugin interface.
CleanseErrorReporter
The validation phase internally holds a list of CleanseError objects that is used to append any validation errors found. We will extract this into a simple class CleanseErrorReporter that can be used to append errors internally, and via the plugin interface.
At the end of validation, the report() function will be called and throw an exception if any errors have been reported. This will include the entire list of errors from all plugins and internal validations and currently does not support any short-circuiting.
ComponentFactory.of(_:validate:serviceLoader:)
The entry point function into Cleanse, ComponentFactory.of(_:validate:) will have an additional parameter for loading our plugins registered with a CleanseServiceLoader instance.
However, these public API changes will be backwards compatible with existing Cleanse projects since the parameter includes a default value set to an empty instance of the CleanseServiceLoader with no plugins.
Impotant: The plugins registered from the service loader will only be run if validate is set to true.
Future Direction
Future additions of the Cleanse SPI could allow plugins write-access into the dependency graph. This would allow developers to delete, modify, or create dependencies on the fly.
This could come in the form of extending the parameters of ComponentBinding to be settable properties or possibly as an entirely different plugin setup distinct from read-only plugins.
Revisions
11/1: Initial Draft
The text was updated successfully, but these errors were encountered:
Cleanse: Service Provider Interface
Author: sebastianv1
Date: 10/11/2019
Links:
Service Provider Interface (SPI)
Dagger SPI
Introduction
One of the benefits of a dependency injection framework is that it provides extra tooling and features for dependency analysis. Cleanse has a few dynamic analysis tools such as cycle detection, component validation, and individual binding validation. These tools operate in the same way by traversing the object graph, catching any errors along the way, and reporting them back to the user.
Although features like cycle detection and component validation make sense to belong inside the core Cleanse framework, building every new feature or tooling that operates over the dependency graph isn't feasible and expands the overall size of the framework (one of the goals of Cleanse is to remain as lightweight as possible). Likewise, there isn't a way for developers to also specify application specific rules about their dependency graph unless they fork the framework and code them in themselves.
Proposed Solution
We propose defining a new public interface that allows developers to create their own validations, features, or tools that hook into the Cleanse object graph. For now, this will be a read-only plugin.
Creating a plugin is done in three steps, first by creating an object that conforms to the protocol
CleanseBindingPlugin
and implementing the required function:func visit(root: ComponentBinding, errorReporter: CleanseErrorReporter)
For example, let's say we're creating a plugin to visualize our dependency graph via Graphviz.
Then we register our plugin with a
CleanseServiceLoader
instance.And finally inject our service loader instance into the root builder function
ComponetFactory.of(_:validate:serviceLoader:)
.Inside our
GraphvizPlugin
implementation, the entry functionvisit(root:errorReporter)
for our plugin is handed a representation of the graph's root componentComponentInfo
, and aCleanseErrorReporter
. TheComponentInfo
instance holds all the information required to traverse over the entire dependency graph, and theCleanseErrorReporter
can be used to append errors to report back to the user after validation.Example Usage
Application specific dependency validation
Consumers of Cleanse may have specific rule sets about their dependency graph for quality or security reasons. One example might be a deprecated class that a developer wants to make sure isn't accidentally included in the dependency graph by his/her other developers.
Other Use Cases
determining if a dependency exist, printing all incoming edges to a dependency, or printing all outgoing edges from a dependency.
Detailed Design
CleanseBindingPlugin
The public service interface is a protocol with one required function.
This function is the entry point for our plugin and will be called during the validation step of building our Cleanse object graph.
ComponentBinding
The
ComponentBinding
holds all the necessary details to describe a dependency graph (or subgraph).Internally, the class
ComponentInfo
will conform toComponentBinding
to provide the implementation. The decision to expose a public protocol instead of raising the access control ofComponentInfo
gives us the flexibility in the future to change the underlying implementation.ComponentInfo
was implemented alongside the existing dynamic validation features Cleanse provides and is a suitable implementation forComponentBinding
today, but its implementation is likely to change in the future and we'd like to maintain a consistent and stable API for the plugin interface.CleanseErrorReporter
The validation phase internally holds a list of
CleanseError
objects that is used to append any validation errors found. We will extract this into a simple classCleanseErrorReporter
that can be used to append errors internally, and via the plugin interface.At the end of validation, the
report()
function will be called and throw an exception if any errors have been reported. This will include the entire list of errors from all plugins and internal validations and currently does not support any short-circuiting.ComponentFactory.of(_:validate:serviceLoader:)
The entry point function into Cleanse,
ComponentFactory.of(_:validate:)
will have an additional parameter for loading our plugins registered with aCleanseServiceLoader
instance.However, these public API changes will be backwards compatible with existing Cleanse projects since the parameter includes a default value set to an empty instance of the
CleanseServiceLoader
with no plugins.Impotant: The plugins registered from the service loader will only be run if
validate
is set totrue
.Future Direction
Future additions of the Cleanse SPI could allow plugins write-access into the dependency graph. This would allow developers to delete, modify, or create dependencies on the fly.
This could come in the form of extending the parameters of
ComponentBinding
to be settable properties or possibly as an entirely different plugin setup distinct from read-only plugins.Revisions
11/1: Initial Draft
The text was updated successfully, but these errors were encountered: