All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.evento.application.reference.AggregateReference Maven / Gradle / Ivy

Go to download

Evento Framework - Bundle. The library to build a RECQ System based on Evento Server

The newest version!
package com.evento.application.reference;

import lombok.Getter;
import com.evento.application.utils.ReflectionUtils;
import com.evento.common.messaging.gateway.CommandGateway;
import com.evento.common.messaging.gateway.QueryGateway;
import com.evento.common.modeling.annotations.handler.AggregateCommandHandler;
import com.evento.common.modeling.annotations.handler.EventSourcingHandler;
import com.evento.common.modeling.exceptions.AggregateDeletedError;
import com.evento.common.modeling.exceptions.AggregateInitializedError;
import com.evento.common.modeling.exceptions.AggregateNotInitializedError;
import com.evento.common.modeling.messaging.message.application.DomainCommandMessage;
import com.evento.common.modeling.messaging.message.application.DomainEventMessage;
import com.evento.common.modeling.messaging.payload.DomainCommand;
import com.evento.common.modeling.messaging.payload.DomainEvent;
import com.evento.common.modeling.state.AggregateState;

import java.lang.reflect.Method;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Set;

/**
 * The `AggregateReference` class is responsible for managing aggregate references and handling domain commands and events.
 */
public class AggregateReference extends Reference {

    private final HashMap eventSourcingReferences = new HashMap<>();
    private final HashMap aggregateCommandHandlerReferences = new HashMap<>();

    @Getter
    private final int snapshotFrequency;

    /**
     * Constructs an `AggregateReference`.
     *
     * @param ref              The reference to the aggregate.
     * @param snapshotFrequency The snapshot frequency for the aggregate.
     */
    public AggregateReference(Object ref, int snapshotFrequency) {
        super(ref);
        this.snapshotFrequency = snapshotFrequency;
        for (Method declaredMethod : ref.getClass().getDeclaredMethods()) {
            var esh = declaredMethod.getAnnotation(EventSourcingHandler.class);
            if (esh != null) {
                eventSourcingReferences.put(
                        Arrays.stream(declaredMethod.getParameterTypes())
                                .filter(DomainEvent.class::isAssignableFrom)
                                .findFirst()
                                .map(Class::getSimpleName)
                                .orElseThrow(() -> new IllegalArgumentException("DomainEvent parameter not fount in  " + declaredMethod)),
                        declaredMethod);
                continue;
            }

            var ach = declaredMethod.getAnnotation(AggregateCommandHandler.class);
            if (ach != null) {
                aggregateCommandHandlerReferences.put(Arrays.stream(declaredMethod.getParameterTypes())
                        .filter(DomainCommand.class::isAssignableFrom)
                        .findFirst()
                        .map(Class::getSimpleName)
                        .orElseThrow(() -> new IllegalArgumentException("DomainCommand parameter not fount in  " + declaredMethod)), declaredMethod);
            }
        }
    }

    /**
     * Get the event sourcing handler method for a given event name.
     *
     * @param eventName The name of the event.
     * @return The event sourcing handler method.
     */
    public Method getEventSourcingHandler(String eventName) {
        return eventSourcingReferences.get(eventName);
    }

    /**
     * Get the aggregate command handler method for a given command name.
     *
     * @param commandName The name of the command.
     * @return The aggregate command handler method.
     */
    public Method getAggregateCommandHandler(String commandName) {
        return aggregateCommandHandlerReferences.get(commandName);
    }

    /**
     * Get the set of registered commands associated with this aggregate reference.
     *
     * @return A set of registered command names.
     */
    public Set getRegisteredCommands() {
        return aggregateCommandHandlerReferences.keySet();
    }

    /**
     * Invoke a domain command on the aggregate and update its state.
     *
     * @param cm             The domain command message.
     * @param envelope       The aggregate state envelope.
     * @param eventStream    The collection of domain event messages.
     * @param commandGateway The command gateway for issuing commands.
     * @param queryGateway   The query gateway for querying data.
     * @return The domain event resulting from the command execution.
     * @throws Exception If there is an error during command handling.
     */
    public DomainEvent invoke(
            DomainCommandMessage cm,
            AggregateStateEnvelope envelope,
            Collection eventStream,
            CommandGateway commandGateway,
            QueryGateway queryGateway)
            throws Exception {

        var commandHandler = aggregateCommandHandlerReferences.get(cm.getCommandName());

        if (eventStream.isEmpty() && envelope.getAggregateState() == null && !isAggregateInitializer(commandHandler))
            throw AggregateNotInitializedError.build(cm.getAggregateId());
        if ((!eventStream.isEmpty() || envelope.getAggregateState() != null) && isAggregateInitializer(commandHandler))
            throw AggregateInitializedError.build(cm.getAggregateId());

        for (var em : eventStream) {
            var eh = getEventSourcingHandler(em.getEventName());
            if(eh != null){
                var state = (AggregateState) ReflectionUtils.invoke(getRef(), eh, em.getPayload(), envelope.getAggregateState(), em.getMetadata(),
                        Instant.ofEpochMilli(em.getTimestamp()));
                if (state == null) {
                    state = envelope.getAggregateState();
                }
                envelope.setAggregateState(state);
                if (envelope.getAggregateState().isDeleted())
                    throw AggregateDeletedError.build(cm.getAggregateId());
            }
        }
        var resp =  (DomainEvent) ReflectionUtils.invoke(getRef(), commandHandler,
                cm.getPayload(),
                envelope.getAggregateState(),
                commandGateway,
                queryGateway,
                cm,
                cm.getMetadata(),
                Instant.ofEpochMilli(cm.getTimestamp())
        );
        if (resp == null) {
            throw new IllegalArgumentException("Command handler returned null");
        }
        var eh = getEventSourcingHandler(resp.getClass().getSimpleName());
        var state = eh == null ? null : (AggregateState) ReflectionUtils.invoke(getRef(), eh, resp, envelope.getAggregateState(), cm.getMetadata(),
                Instant.ofEpochMilli(cm.getTimestamp()));
        if (state == null) {
            state = envelope.getAggregateState();
        }
        envelope.setAggregateState(state);
        return resp;
    }

    /**
     * Check if the provided command handler is an aggregate initializer.
     *
     * @param commandHandler The aggregate command handler method.
     * @return `true` if it is an initializer, otherwise `false`.
     */
    private boolean isAggregateInitializer(Method commandHandler) {
        return commandHandler.getAnnotation(AggregateCommandHandler.class).init();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy