Skip to content
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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

CodeDrivenMitch
Copy link
Member

@CodeDrivenMitch CodeDrivenMitch commented Dec 30, 2022

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.

@CodeDrivenMitch CodeDrivenMitch requested review from a team, gklijs and smcvb and removed request for a team December 30, 2022 12:37
@CodeDrivenMitch CodeDrivenMitch self-assigned this Dec 30, 2022
@CodeDrivenMitch CodeDrivenMitch added Type: Enhancement Use to signal an issue enhances an already existing feature of the project. Priority 3: Could Low priority. Issues that are nice to have but have a straightforward workaround. Status: In Progress Use to signal this issue is actively worked on. labels Dec 30, 2022
@CodeDrivenMitch CodeDrivenMitch added this to the Release 4.7.0 milestone Dec 30, 2022
Copy link
Member

@smcvb smcvb left a 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);
Copy link
Member

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.

Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted

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.
@sonarcloud
Copy link

sonarcloud bot commented Jan 9, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 2 Code Smells

87.5% 87.5% Coverage
0.0% 0.0% Duplication

Copy link
Member

@abuijze abuijze left a 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);
Copy link
Member

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.

Copy link
Member Author

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

Copy link
Member Author

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)

@smcvb
Copy link
Member

smcvb commented Jan 20, 2023

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.

Copy link
Member

@abuijze abuijze left a 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();
Copy link
Member

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!");
Copy link
Member

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(
Copy link
Member

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) {
Copy link
Member

@abuijze abuijze Feb 13, 2023

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,
Copy link
Member

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;
Copy link
Member

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?

@smcvb smcvb removed this from the Release 4.8.0 milestone Jun 15, 2023
@smcvb
Copy link
Member

smcvb commented Jun 15, 2023

Time doesn't allow us to introduce this change into 4.8.0. Hence, I've removed the milestone.

@smcvb smcvb added Status: On Hold Use to signal that work on this issue is paused. The description should clarify why. and removed Status: In Progress Use to signal this issue is actively worked on. labels Jun 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority 3: Could Low priority. Issues that are nice to have but have a straightforward workaround. Status: On Hold Use to signal that work on this issue is paused. The description should clarify why. Type: Enhancement Use to signal an issue enhances an already existing feature of the project.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants