quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.74k stars 2.67k forks source link

Request for EntityCallback Support in Quarkus #44066

Open nitinty opened 2 hours ago

nitinty commented 2 hours ago

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 BeforeConvertCallback<T> {
    void beforeConvert(T entity);
}

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

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

public interface AfterSaveCallback<T> {
    void afterSave(T 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
public class CallbackRegistry {
    private final Map<Class<?>, List<Object>> callbacks = new HashMap<>();

    public <T> void registerCallback(Class<T> entityClass, Object callback) {
        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 <T> void invokeCallbacks(T entity, Class<? extends Callback<T>> callbackType) {
        List<Object> callbacks = callbackRegistry.getCallbacks(entity.getClass());
        for (Object callback : callbacks) {
            if (callbackType.isInstance(callback)) {
                ((Callback<T>) callback).invoke(entity);
            }
        }
    }

4. Register Callbacks:

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


@Singleton
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 commented 2 hours ago

@FroMage @loicmathieu @xstefank

I have opened this issue regarding to #43989 discussion.