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

Request for EntityCallback Support in Quarkus MongoDB Panache #44066

Open
nitinty opened this issue Oct 24, 2024 · 10 comments
Open

Request for EntityCallback Support in Quarkus MongoDB Panache #44066

nitinty opened this issue Oct 24, 2024 · 10 comments
Labels
area/mongodb kind/enhancement New feature or request

Comments

@nitinty
Copy link

nitinty commented Oct 24, 2024

Description

Summary: I would like to propose the addition of EntityCallback support in Quarkus, similar to the functionality provided by Spring. Specifically, I am looking for the following callbacks:

AfterConvertCallback: Invoked after an entity is converted from its database representation to its domain representation.

BeforeConvertCallback: Triggered before an entity is converted to its database representation.

AfterSaveCallback: Occurs after an entity is saved to the database.

BeforeSaveCallback: Invoked before an entity is saved to the database.

Use Cases: Implementing these callbacks would allow developers to execute custom logic at different stages of the entity lifecycle, enhancing flexibility and maintainability.

Implementation ideas

I’m adding a rough implementation idea here for adding EntityCallback support in Quarkus MongoDB Panache. Please feel free to update or suggest any better approaches!

Implementation Ideas for EntityCallback Support in Quarkus MongoDB Panache

1. Define Callback Interfaces:

Create interfaces for each callback (e.g., BeforeConvertCallback, AfterConvertCallback, BeforeSaveCallback, AfterSaveCallback).
Each interface can have a method that accepts the entity type as a parameter.

public interface Callback<T> {
    void invoke(T entity);
}


public interface AfterConvertCallback<T> extends Callback<T>{
    void afterConvert(T entity);

    @Override
    default void invoke(T entity) {
        afterConvert(entity);
    }
}


public interface AfterSaveCallback<T> extends Callback<T> {
    void afterSave(T entity);
    @Override
    default void invoke(T entity) {
        afterSave(entity);
    }
}


public interface BeforeConvertCallback<T> extends Callback<T>{
    void beforeConvert(T entity);

    @Override
    default void invoke(T entity) {
        beforeConvert(entity);
    }
}


public interface BeforeSaveCallback<T> extends Callback<T>{
    void beforeSave(T entity);

    @Override
    default void invoke(T entity) {
        beforeSave(entity);
    }
}

2. Implement a Callback Registry:

Create a registry to hold all registered callbacks. This could be done using a Map<Class<?>, List> to group callbacks by entity type.

@Singleton
@Unremovable
public class CallbackRegistry {
    private final Map<Class<?>, List<Object>> callbacks = new HashMap<>();

    public <T> void registerCallback(Class<T> entityClass, Object callback) {
        System.out.println("CallbackRegistry :: registerCallback with " + entityClass.getSimpleName());
        callbacks.computeIfAbsent(entityClass, k -> new ArrayList<>()).add(callback);
    }

    public <T> List<Object> getCallbacks(Class<T> entityClass) {
        return callbacks.getOrDefault(entityClass, Collections.emptyList());
    }
}

3. Enhance the Panache Repository:

Enhance the PanacheMongoRepository / ReactivePanacheMongoRepository (MongoOperations / ReactiveMongoOperations)

    public void persist(Object entity) {
        invokeCallbacks(entity, BeforeSaveCallback.class);
        MongoCollection collection = mongoCollection(entity);
        persist(collection, entity);
        invokeCallbacks(entity, AfterSaveCallback.class);
    }


    private  void invokeCallbacks(Object entity, Class<? extends Callback> callbackType) {
        List<Object> callbacks = callbackRegistry.getCallbacks(entity.getClass());
        for (Object callback : callbacks) {
            if (callbackType.isInstance(callback)) {
                ((Callback) callback).invoke(entity);
            }
        }
    }

4. Register Callbacks:

Allow users to register callbacks using CDI (Contexts and Dependency Injection) annotations, such as @singleton or @ApplicationScoped.

@Singleton
@Unremovable
public class MyEntityCallbacks implements BeforeSaveCallback<MyEntity>, AfterSaveCallback<MyEntity> {

    @Inject
    CallbackRegistry callbackRegistry;

    public MyEntityCallbacks() {
        callbackRegistry.registerCallback(MyEntity.class, this);
    }

    @Override
    public void beforeSave(MyEntity entity) {
        // Custom logic before saving
    }

    @Override
    public void afterSave(MyEntity entity) {
        // Custom logic after saving
    }
}

5. Testing

Implement component test cases to verify that the callbacks are invoked correctly during the entity lifecycle.

@nitinty nitinty added the kind/enhancement New feature or request label Oct 24, 2024
@nitinty
Copy link
Author

nitinty commented Oct 24, 2024

@FroMage @loicmathieu @xstefank

I have opened this issue regarding to #43989 discussion.

@nitinty nitinty changed the title Request for EntityCallback Support in Quarkus Request for EntityCallback Support in Quarkus MongoDB Panache Oct 24, 2024
@FroMage
Copy link
Member

FroMage commented Oct 24, 2024

I'd rather follow the same semantics as JPA lifecycle listeners, using annotations: https://jakarta.ee/specifications/persistence/3.2/jakarta-persistence-spec-3.2#lifecycle-callback-methods

I could not find any similar lifecyle listener support in Jakarta Data, though perhaps @gavinking can contradict me.

@nitinty
Copy link
Author

nitinty commented Oct 24, 2024

I'd rather follow the same semantics as JPA lifecycle listeners, using annotations: https://jakarta.ee/specifications/persistence/3.2/jakarta-persistence-spec-3.2#lifecycle-callback-methods

I could not find any similar lifecyle listener support in Jakarta Data, though perhaps @gavinking can contradict me.

Yes you can.

I mentioned this with the intention of giving developers more freedom to separate any logic from the entity class, keeping it clean and focused. It also allows custom logic to reside in a separate class for better organization.

@gavinking
Copy link

I could not find any similar lifecyle listener support in Jakarta Data, though perhaps @gavinking can contradict me.

In our implementation of Jakarta Data, you can hook into the, @PostPersist, @PostUpdate, and @PostRemove events, which are triggered by the underlying stateless session.

My proposal for EntityAgent includes a set of events that properly model the stateless operations of a stateless session.

jakartaee/persistence#675

There is not yet any firm plan to add Jakarta Data-specific events.

@gavinking
Copy link

Of course, if you want something higher-level and better-integrated with CDI, then what you are looking for is integration with the CDI event bus. (Much netter than inventing a whole new eventing infrastructure as proposed above.)

It should be quite straightforward to write a JPA EventListener which raises CDI events. And then CDI components can received them via @Observes.

I'm sure I've displayed an example implementation of such an EventListener before.

@gavinking
Copy link

There is not yet any firm plan to add Jakarta Data-specific events.

There is now. See my proposal here: jakartaee/data#876

@FroMage
Copy link
Member

FroMage commented Oct 24, 2024

That was fast 😅

Copy link

quarkus-bot bot commented Oct 25, 2024

/cc @loicmathieu (mongodb)

@nitinty
Copy link
Author

nitinty commented Oct 29, 2024

@loicmathieu @FroMage
Could you please provide an update on whether the request to add EntityCallback support in Quarkus MongoDB Panache is being considered ?

@loicmathieu
Copy link
Contributor

I think if this would be added to Jakarta Data, it would be a good idea to implement something similar.

But for the moment, Panache is not tied to Jakarta Data, and I'm not sure we want to wait for it to be available, but we can still implement the same and then move to the new event classes when they exist.

And of course, we would need that to be implemented for both Hibernate with Panache and MongoDB with Panache.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/mongodb kind/enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants