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

Inconsistent implementation of MutablePersistable #207

Closed
th3n3rd opened this issue Dec 16, 2023 · 2 comments
Closed

Inconsistent implementation of MutablePersistable #207

th3n3rd opened this issue Dec 16, 2023 · 2 comments
Assignees
Labels
module: bytebuddy ByteBuddy plugin type: bug Bug
Milestone

Comments

@th3n3rd
Copy link

th3n3rd commented Dec 16, 2023

The current implementation of PersistableImplementor generates byte-code that a typical compiler probably would not.

Let's take one of the sample classes part of the test suite:

@Getter
public class SampleAggregate implements AggregateRoot<SampleAggregate, SampleAggregateIdentifier> {

	private final SampleAggregateIdentifier id;
	
        // ... other non-relevant stuff

	public SampleAggregate(SampleAggregateIdentifier id) {
		this.id = id;
	}

        // ... other non-relevant stuff
}

The above will generate byte-code with the following problems:

  1. It will incorrectly implement MutablePersistable, lacking the implementation for the __jMolecules__markNotNew method
  2. it will have an additional default constructor despite the final fields that need initialisation, in the original code

Suggestions:

The first problem might be fixable by always generating the missing implementation, instead of being conditional to a given callbackInterface.

e.g.

JMoleculesType implementPersistable(JMoleculesType type) {
    // ...
    return type.findIdField().map(field -> {
        // ...
        return type
            .implement(MutablePersistable.class, type.getTypeDescription(), field.getType())
            .mapBuilder(this::generateCallbacks)
            // fix (1): added the following instruction to generate the method regardless
            .mapBuilder(it -> !it.hasMethod(named(MARK_NOT_NEW_METHOD)), this::generateMarkNotNewMethod)
            .mapBuilder(it -> !it.hasField(named(IS_NEW_FIELD)), this::generateIsNewField)
            .mapBuilder(it -> !it.hasMethod(isEquals()), it -> generateEquals(it, isIdField))
            .mapBuilder(it -> !it.hasMethod(isHashCode()), it -> generateHashCode(it, isIdField))
            .mapBuilder(it -> !it.hasMethod(hasMethodName(IS_NEW_METHOD)), this::generateIsNewMethod);
        }).orElse(type);
}

// ...

private Builder<?> generateCallbacks(Builder<?> builder) {
    if (options.callbackInterface != null) {
        // fix (1): simplified as the method generation moved earlier
        builder = builder.require(createCallbackComponent(builder.toTypeDescription()));
    }
    
    // ...

    return builder;
}

Not sure how to fix (2) though.

@odrotbohm
Copy link
Member

It will incorrectly implement MutablePersistable, lacking the implementation for the __jMolecules__markNotNew method

Can you elaborate on that? I don't see how you come to that, as the code you show below checks for the presence of the method, and, if not present, generates it.

it will have an additional default constructor despite the final fields that need initialisation, in the original code

That's correct but mandated by JPA. None of the code generated here will be callable from user code, though.

@odrotbohm odrotbohm self-assigned this Dec 19, 2023
@odrotbohm odrotbohm added the lifecycle:waiting-for-feedback Feedback from the original reporter(s) required label Dec 19, 2023
@th3n3rd
Copy link
Author

th3n3rd commented Dec 22, 2023

Can you elaborate on that? I don't see how you come to that, as the code you show below checks for the presence of the method, and, if not present, generates it.

The code that I pasted in there is a possible fix, the current version doesn't look like that.

The latest version available here generates the method conditionally on whether callbackInterface has been provided or not. In cases where the callbackInterface is not provided the method is not getting generated, hence the interface not implemented properly.

odrotbohm added a commit that referenced this issue Dec 23, 2023
This will allow us to verify the correct code generation per technology.
odrotbohm added a commit that referenced this issue Dec 23, 2023
In case of annotation based callbacks to flip the is new state of an aggregate we currently implement MutablePersistable but do not actually generate an implementation for the method declared by that interface. This doesn't seem to cause any problems currently as nobody is ever invoking that method (as the flipping is implemented through the annotation-based callbacks).

We now avoid implementing MutablePersistable for on-entity callbacks and fall back to only implementing Persistable, a getId() and isNew() method (the latter already generated before).
odrotbohm added a commit that referenced this issue Dec 25, 2023
…ation tests.

To make sure it gets included when running integration tests against Spring Boot 3.*.
odrotbohm added a commit that referenced this issue Dec 25, 2023
@odrotbohm odrotbohm added this to the 0.19 milestone Dec 31, 2023
@odrotbohm odrotbohm added type: bug Bug module: bytebuddy ByteBuddy plugin and removed lifecycle:waiting-for-feedback Feedback from the original reporter(s) required labels Dec 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
module: bytebuddy ByteBuddy plugin type: bug Bug
Projects
None yet
Development

No branches or pull requests

2 participants