New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce component decorators #2537
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple of small remarks, mainly documentation. Other than that, this looks good to me.
@FunctionalInterface | ||
public interface ComponentDecorator<T> { | ||
|
||
T decorate(Configuration configuration, T component); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This public method deserves documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume these changes are still pending?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adjusted
config/src/main/java/org/axonframework/config/DefaultConfigurer.java
Outdated
Show resolved
Hide resolved
config/src/main/java/org/axonframework/config/DefaultConfigurer.java
Outdated
Show resolved
Hide resolved
You can now decorate `Component` instances by calling `Configurer.registerComponentDecorator`. Upon initialization of the component, the original component will be constructed and the decorators will be called in order. Decorators can return the existing component, a modified version of it or a completely different implementation, as long as it's the same type.
c7b0fa5
to
48e5e0b
Compare
Kudos, SonarCloud Quality Gate passed! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty good. Have a few recommendations for changes, though
Function<Configuration, ? extends B> previous = builderFunction; | ||
this.update((configuration) -> { | ||
B wrappedComponent = previous.apply(configuration); | ||
LifecycleHandlerInspector.registerLifecycleHandlers(configuration, wrappedComponent); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line makes me wonder: should we always call this, or should we rely on the decorator to do this? In the latter case, a decorator could influence the startup process. For example to implement some lazy initialization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want a decorator to have that much power? I am afraid of abuse. In addition, I feel a decorator is there to enhance a component as-is, without modifying the original functionality.
It does open up some possibilities though, like you say! But it's already easy to miss
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Configurer.registerComponentDecorator
takes as an argument whether the user wants the lifecycle handlers called. If the boolean is not supplied, they are (by an overloaded method)
config/src/main/java/org/axonframework/config/ComponentDecorator.java
Outdated
Show resolved
Hide resolved
config/src/main/java/org/axonframework/config/DefaultConfigurer.java
Outdated
Show resolved
Hide resolved
…omponent when initializing
Moving this pull request to release 4.8.0, so we can implement a workable integration with Spring for decorators on Axon Framework's infrastructure components. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some concerns about the lifecycle handler registration proposal. I'm afraid this implementation could lead to excessive registration of default handlers.
EDIT - Should have checked a second time. Because the original instance is swapped with the decorated one, the lifecycle handlers will only be called once. 🤦
* | ||
* @return the initialized component contained in this instance | ||
*/ | ||
public B get() { | ||
if (instance == null) { | ||
Configuration configuration = configSupplier.get(); | ||
instance = builderFunction.apply(configuration); | ||
List<ComponentDecorator> decorators = configuration != null ? configuration.getDecorators() : Collections.emptyList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious why the null-check is here. It really shouldn't be null, as the configuration is passed as a parameter to the builder function. A null configuration at this point should be a configuration problem.
@@ -412,6 +412,10 @@ default ScopeAwareProvider scopeAwareProvider() { | |||
*/ | |||
EventUpcasterChain upcasterChain(); | |||
|
|||
default List<ComponentDecorator> getDecorators() { | |||
throw new AxonConfigurationException("The getDecorators has not been implemented by the Configuration!"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's not implemented, wouldn't the default behavior of returning an empty list suffice?
@Nonnull BiFunction<Configuration, C, C> decoratorFunction, | ||
boolean registerOriginalLifeCycleHandlers | ||
) { | ||
throw new AxonConfigurationException( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this API isn't designed to be implemented by others, but rather for the abstraction of the implementation, I suggest we don't provide a default implementation of this method.
public Object decorate(Configuration configuration, Object component) { | ||
if (componentClass.isAssignableFrom(component.getClass())) { | ||
T result = decorator.apply(configuration, (T) component); | ||
if (registerOriginalLifeCycleHandlers) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if a component is decorated more than once? In that case the registerLifecycleHandlers
method would be invoked multiple times, and the component would perhaps be initialized multiple times.
Shouldn't be the decorator's responsibility to also decorate calls to Lifecycle.registerLifecycleHandlers
?
* @param <C> The declared type of component | ||
* @return the current instance of the Configurer, for chaining purposes | ||
*/ | ||
default <C> Configurer registerComponentDecorator(@Nonnull Class<C> componentType, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed the order of parameters here is different than for the constructor of the ComponentTypeSafeDecorator. The boolean is probably last here as it is an optional parameter?
|
||
@Override | ||
public List<ComponentDecorator> getDecorators() { | ||
return decorators; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't we return a Collections.unmodifiableList
to make sure the list isn't modified externally?
Time doesn't allow us to introduce this change into 4.8.0. Hence, I've removed the milestone. |
You can now decorate
Component
instances by callingConfigurer.registerComponentDecorator
. Upon initialization of the component, the original component will be constructed and the decorators will be called in order. Decorators can return the existing component, a modified version of it or a completely different implementation, as long as it's the same type.