spring-data-relational icon indicating copy to clipboard operation
spring-data-relational copied to clipboard

Use event interface in entity class

Open sergeevik opened this issue 9 months ago • 7 comments

The current event model requests the creation of a bean component to implement the child AbstractRelationalEvent (AfterSaveEvent / BeforeConvertEvent / ...)

If the event logic is strongly related to the entity, then now you have to divide them into classes, perhaps make some private attributes public.

You can add processing logic if the entity itself implements these interfaces. Do not publish the event (or with publish), but call the method on the entity If the event model will check the implementation of the interface and call it instead of or together with sending the event, then it will be possible to combine the entity and its reaction to events in one class

for example something like this:

package org.springframework.data.jdbc.core;
...
public class JdbcAggregateTemplate implements JdbcAggregateOperations {
...

	private <T> T triggerBeforeConvert(T aggregateRoot) {
		if (aggregateRoot instanceof BeforeConvertCallback) {
			return ((BeforeConvertCallback<T>) aggregateRoot).onBeforeConvert(aggregateRoot);
		}
		eventDelegate.publishEvent(() -> new BeforeConvertEvent<>(aggregateRoot));
		return entityCallbacks.callback(BeforeConvertCallback.class, aggregateRoot);
	}

sergeevik avatar Mar 19 '25 20:03 sergeevik

hello! could i work on?

ghgh415263 avatar Nov 04 '25 06:11 ghgh415263

Sure.

schauder avatar Nov 04 '25 06:11 schauder

hello i have question.

according to example

private <T> T triggerBeforeConvert(T aggregateRoot) {
		if (aggregateRoot instanceof BeforeConvertCallback) {                // new 
			return ((BeforeConvertCallback<T>) aggregateRoot).onBeforeConvert(aggregateRoot);
		}
		eventDelegate.publishEvent(() -> new BeforeConvertEvent<>(aggregateRoot));    // previously existing code
		return entityCallbacks.callback(BeforeConvertCallback.class, aggregateRoot);
}

if (aggregateRoot instanceof BeforeConvertCallback) is true previously existing code is not excuted is this the right design? or would it be better to allow the existing code to run as well? in my opinion, event should still be published through eventDelegate

like below

private <T> T triggerBeforeConvert(T aggregateRoot) {
                eventDelegate.publishEvent(() -> new BeforeConvertEvent<>(aggregateRoot));
		if (aggregateRoot instanceof BeforeConvertCallback) { 
			return ((BeforeConvertCallback<T>) aggregateRoot).onBeforeConvert(aggregateRoot);
		}
		return entityCallbacks.callback(BeforeConvertCallback.class, aggregateRoot);
}

ghgh415263 avatar Nov 05 '25 03:11 ghgh415263

events and callbacks should still get triggered/called.

And either all before the aggregate root or all after, but certainly not some before some after.

Actually it probably should just use the callback mechanism, so ordering can be controlled in the usual ways.

schauder avatar Nov 05 '25 06:11 schauder

hi !

if an entity directly implements a callback interface, it should also be included in the entityCallbacks?

ghgh415263 avatar Nov 11 '25 14:11 ghgh415263

Yes. Basically the entity becomes just another callback implementation.

schauder avatar Nov 11 '25 15:11 schauder

hello. thank for quick response !

Ultimately, the design needs to satisfy these requirements:

  1. Callback implemented in the entity class must be treated the same way as other callbacks registered in EntityCallbacks, including participating in the same ordering mechanism.

  2. Callback implemented in the entity class must be able to access the entity’s private methods and fields.

I plan to register the following object in advance for each entity that implements an entity callback.

Is there a better approach, or are there any issues with this approach?


public final class DelegatingBeforeSaveCallback<T>
        implements BeforeSaveCallback<T>, Ordered {

    private final int order;

    public DelegatingBeforeSaveCallback(int order) {
        this.order = order;
    }

    @Override
    public int getOrder() {
        return order;
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public T onBeforeSave(T aggregate, MutableAggregateChange<T> aggregateChange) {

        if (aggregate instanceof BeforeSaveCallback rawCallback) {
            return (T) rawCallback.onBeforeSave(aggregate, aggregateChange);
        }

        return aggregate;
    }
}

ghgh415263 avatar Dec 12 '25 13:12 ghgh415263