Package org.axonframework.commandhandling.disruptor

Source Code of org.axonframework.commandhandling.disruptor.DisruptorCommandBus$ExceptionHandler

/*
* Copyright (c) 2010-2014. Axon Framework
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.axonframework.commandhandling.disruptor;

import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.EventHandlerGroup;
import org.axonframework.commandhandling.CommandBus;
import org.axonframework.commandhandling.CommandCallback;
import org.axonframework.commandhandling.CommandDispatchInterceptor;
import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.commandhandling.CommandHandlerInterceptor;
import org.axonframework.commandhandling.CommandMessage;
import org.axonframework.commandhandling.CommandTargetResolver;
import org.axonframework.commandhandling.interceptors.SerializationOptimizingInterceptor;
import org.axonframework.common.Assert;
import org.axonframework.common.AxonThreadFactory;
import org.axonframework.domain.DomainEventStream;
import org.axonframework.eventhandling.EventBus;
import org.axonframework.eventsourcing.AggregateFactory;
import org.axonframework.eventsourcing.EventSourcedAggregateRoot;
import org.axonframework.eventsourcing.EventStreamDecorator;
import org.axonframework.eventstore.EventStore;
import org.axonframework.repository.Repository;
import org.axonframework.serializer.Serializer;
import org.axonframework.unitofwork.TransactionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* Asynchronous CommandBus implementation with very high performance characteristics. It divides the command handling
* process in two steps, which can be executed in different threads. The CommandBus is backed by a {@link Disruptor},
* which ensures that two steps are executed sequentially in these threads, while minimizing locking and inter-thread
* communication.
* <p/>
* The process is split into two separate steps, each of which is executed in a different thread:
* <ol>
* <li><em>Command Handler execution</em><br/>This process invokes the command handler with the incoming command. The
* result and changes to the aggregate are recorded for the next step.</li>
* <li><em>Event storage and publication</em><br/>This process stores all generated domain events and publishes them
* (with any optional application events) to the event bus. Finally, an asynchronous task is scheduled to invoke the
* command handler callback with the result of the command handling result.</li>
* </ol>
* <p/>
* <em>Exceptions and recovery</em>
* <p/>
* This separation of process steps makes this implementation very efficient and highly performing. However, it does
* not cope with exceptions very well. When an exception occurs, an Aggregate that has been loaded is potentially
* corrupt. That means that an aggregate does not represent a state that can be reproduced by replaying its committed
* events. Although this implementation will recover from this corrupt state, it may result in a number of commands
* being rejected in the meantime. These command may be retried if the cause of the {@link
* AggregateStateCorruptedException} does not indicate a non-transient error.
* <p/>
* Commands that have been executed against a potentially corrupt Aggregate will result in a {@link
* AggregateStateCorruptedException} exception. These commands are automatically rescheduled for processing by
* default. Use {@link DisruptorConfiguration#setRescheduleCommandsOnCorruptState(boolean)} disable this feature. Note
* that the order in which commands are executed is not fully guaranteed when this feature is enabled (default).
* <p/>
* <em>Limitations of this implementation</em>
* <p/>
* Although this implementation allows applications to achieve extreme performance (over 1M commands on commodity
* hardware), it does have some limitations. It only allows a single aggregate to be invoked during command processing.
* <p/>
* This implementation can only work with Event Sourced Aggregates.
* <p/>
* <em>Infrastructure considerations</em>
* <p/>
* This CommandBus implementation has special requirements for the Repositories being used during Command Processing.
* Therefore, the Repository instance to use in the Command Handler must be created using {@link
* #createRepository(org.axonframework.eventsourcing.AggregateFactory)}.
* Using another repository will most likely result in undefined behavior.
* <p/>
* The DisruptorCommandBus must have access to at least 3 threads, two of which are permanently used while the
* DisruptorCommandBus is operational. At least one additional thread is required to invoke callbacks and initiate a
* recovery process in the case of exceptions.
* <p/>
* Consider providing an alternative {@link org.axonframework.domain.IdentifierFactory} implementation. The default
* implementation used {@link java.util.UUID#randomUUID()} to generated identifier for Events. The poor performance of
* this method severely impacts overall performance of the DisruptorCommandBus. A better performing alternative is, for
* example, <a href="http://johannburkard.de/software/uuid/" target="_blank"><code>com.eaio.uuid.UUID</code></a>
*
* @author Allard Buijze
* @since 2.0
*/
public class DisruptorCommandBus implements CommandBus {

    private static final Logger logger = LoggerFactory.getLogger(DisruptorCommandBus.class);
    private static final ThreadGroup DISRUPTOR_THREAD_GROUP = new ThreadGroup("DisruptorCommandBus");

    private final ConcurrentMap<String, CommandHandler<?>> commandHandlers =
            new ConcurrentHashMap<String, CommandHandler<?>>();
    private final Disruptor<CommandHandlingEntry> disruptor;
    private final CommandHandlerInvoker[] commandHandlerInvokers;
    private final List<CommandDispatchInterceptor> dispatchInterceptors;
    private final List<CommandHandlerInterceptor> invokerInterceptors;
    private final List<CommandHandlerInterceptor> publisherInterceptors;
    private final ExecutorService executorService;
    private final boolean rescheduleOnCorruptState;
    private volatile boolean started = true;
    private volatile boolean disruptorShutDown = false;
    private final long coolingDownPeriod;
    private final CommandTargetResolver commandTargetResolver;
    private final int publisherCount;
    private final int serializerCount;
    private final CommandCallback<Object> failureLoggingCallback = new FailureLoggingCommandCallback();

    /**
     * Initialize the DisruptorCommandBus with given resources, using default configuration settings. Uses a Blocking
     * WaitStrategy on a RingBuffer of size 4096. The (2) Threads required for command execution are created
     * immediately. Additional threads are used to invoke response callbacks and to initialize a recovery process in
     * the case of errors.
     *
     * @param eventStore The EventStore where generated events must be stored
     * @param eventBus   The EventBus where generated events must be published
     */
    public DisruptorCommandBus(EventStore eventStore, EventBus eventBus) {
        this(eventStore, eventBus, new DisruptorConfiguration());
    }

    /**
     * Initialize the DisruptorCommandBus with given resources and settings. The Threads required for command
     * execution are immediately requested from the Configuration's Executor, if any. Otherwise, they are created.
     *
     * @param eventStore    The EventStore where generated events must be stored
     * @param eventBus      The EventBus where generated events must be published
     * @param configuration The configuration for the command bus
     */
    @SuppressWarnings("unchecked")
    public DisruptorCommandBus(EventStore eventStore, EventBus eventBus,
                               DisruptorConfiguration configuration) {
        Assert.notNull(eventStore, "eventStore may not be null");
        Assert.notNull(eventBus, "eventBus may not be null");
        Assert.notNull(configuration, "configuration may not be null");
        Executor executor = configuration.getExecutor();
        if (executor == null) {
            executorService = Executors.newCachedThreadPool(
                    new AxonThreadFactory(DISRUPTOR_THREAD_GROUP));
            executor = executorService;
        } else {
            executorService = null;
        }
        rescheduleOnCorruptState = configuration.getRescheduleCommandsOnCorruptState();
        invokerInterceptors = new ArrayList<CommandHandlerInterceptor>(configuration.getInvokerInterceptors());
        publisherInterceptors = new ArrayList<CommandHandlerInterceptor>(configuration.getPublisherInterceptors());
        dispatchInterceptors = new ArrayList<CommandDispatchInterceptor>(configuration.getDispatchInterceptors());
        TransactionManager transactionManager = configuration.getTransactionManager();
        disruptor = new Disruptor<CommandHandlingEntry>(
                new CommandHandlingEntry.Factory(configuration.getTransactionManager() != null),
                configuration.getBufferSize(),
                executor,
                configuration.getProducerType(),
                configuration.getWaitStrategy());
        commandTargetResolver = configuration.getCommandTargetResolver();

        // configure invoker Threads
        commandHandlerInvokers = initializeInvokerThreads(eventStore, configuration);
        // configure serializer Threads
        SerializerHandler[] serializerThreads = initializeSerializerThreads(configuration);
        serializerCount = serializerThreads.length;
        // configure publisher Threads
        EventPublisher[] publishers = initializePublisherThreads(eventStore, eventBus, configuration, executor,
                                                                 transactionManager);
        publisherCount = publishers.length;
        disruptor.handleExceptionsWith(new ExceptionHandler());

        EventHandlerGroup<CommandHandlingEntry> eventHandlerGroup = disruptor.handleEventsWith(commandHandlerInvokers);
        if (serializerThreads.length > 0) {
            eventHandlerGroup = eventHandlerGroup.then(serializerThreads);
            invokerInterceptors.add(new SerializationOptimizingInterceptor());
        }
        eventHandlerGroup.then(publishers);

        coolingDownPeriod = configuration.getCoolingDownPeriod();
        disruptor.start();
    }

    private EventPublisher[] initializePublisherThreads(EventStore eventStore, EventBus eventBus,
                                                        DisruptorConfiguration configuration, Executor executor,
                                                        TransactionManager transactionManager) {
        EventPublisher[] publishers = new EventPublisher[configuration.getPublisherThreadCount()];
        for (int t = 0; t < publishers.length; t++) {
            publishers[t] = new EventPublisher(eventStore, eventBus, executor, transactionManager,
                                               configuration.getRollbackConfiguration(), t);
        }
        return publishers;
    }

    private SerializerHandler[] initializeSerializerThreads(DisruptorConfiguration configuration) {
        if (!configuration.isPreSerializationConfigured()) {
            return new SerializerHandler[0];
        }
        Serializer serializer = configuration.getSerializer();
        SerializerHandler[] serializerThreads = new SerializerHandler[configuration.getSerializerThreadCount()];
        for (int t = 0; t < serializerThreads.length; t++) {
            serializerThreads[t] = new SerializerHandler(serializer, t, configuration.getSerializedRepresentation());
        }
        return serializerThreads;
    }

    private CommandHandlerInvoker[] initializeInvokerThreads(EventStore eventStore,
                                                             DisruptorConfiguration configuration) {
        CommandHandlerInvoker[] invokers;
        invokers = new CommandHandlerInvoker[configuration.getInvokerThreadCount()];
        for (int t = 0; t < invokers.length; t++) {
            invokers[t] = new CommandHandlerInvoker(eventStore, configuration.getCache(), t);
        }
        return invokers;
    }

    @Override
    public void dispatch(final CommandMessage<?> command) {
        dispatch(command, failureLoggingCallback);
    }

    @Override
    public <R> void dispatch(CommandMessage<?> command, CommandCallback<R> callback) {
        Assert.state(started, "CommandBus has been shut down. It is not accepting any Commands");
        CommandMessage<?> commandToDispatch = command;
        for (CommandDispatchInterceptor interceptor : dispatchInterceptors) {
            commandToDispatch = interceptor.handle(commandToDispatch);
        }
        doDispatch(commandToDispatch, callback);
    }

    /**
     * Forces a dispatch of a command. This method should be used with caution. It allows commands to be retried during
     * the cooling down period of the disruptor.
     *
     * @param command  The command to dispatch
     * @param callback The callback to notify when command handling is completed
     * @param <R>      The expected return type of the command
     */
    public <R> void doDispatch(CommandMessage command, CommandCallback<R> callback) {
        Assert.state(!disruptorShutDown, "Disruptor has been shut down. Cannot dispatch or re-dispatch commands");
        RingBuffer<CommandHandlingEntry> ringBuffer = disruptor.getRingBuffer();
        int invokerSegment = 0;
        int publisherSegment = 0;
        int serializerSegment = 0;
        if ((commandHandlerInvokers.length > 1 || publisherCount > 1 || serializerCount > 1)) {
            Object aggregateIdentifier = commandTargetResolver.resolveTarget(command).getIdentifier();
            if (aggregateIdentifier != null) {
                int idHash = aggregateIdentifier.hashCode() & Integer.MAX_VALUE;
                if (commandHandlerInvokers.length > 1) {
                    invokerSegment = idHash % commandHandlerInvokers.length;
                }
                if (serializerCount > 1) {
                    serializerSegment = idHash % serializerCount;
                }
                if (publisherCount > 1) {
                    publisherSegment = idHash % publisherCount;
                }
            }
        }
        long sequence = ringBuffer.next();
        CommandHandlingEntry event = ringBuffer.get(sequence);
        event.reset(command, commandHandlers.get(command.getCommandName()), invokerSegment, publisherSegment,
                    serializerSegment, new BlacklistDetectingCallback<R>(callback, command, disruptor.getRingBuffer(),
                                                                         this, rescheduleOnCorruptState),
                    invokerInterceptors, publisherInterceptors
        );
        ringBuffer.publish(sequence);
    }

    /**
     * Creates a repository instance for an Event Sourced aggregate that is created by the given
     * <code>aggregateFactory</code>.
     * <p/>
     * The repository returned must be used by Command Handlers subscribed to this Command Bus for loading aggregate
     * instances. Using any other repository instance may result in undefined outcome (a.k.a. concurrency problems).
     *
     * @param aggregateFactory The factory creating uninitialized instances of the Aggregate
     * @param <T>              The type of aggregate to create the repository for
     * @return the repository that provides access to stored aggregates
     */
    public <T extends EventSourcedAggregateRoot> Repository<T> createRepository(AggregateFactory<T> aggregateFactory) {
        return createRepository(aggregateFactory, NoOpEventStreamDecorator.INSTANCE);
    }

    /**
     * Creates a repository instance for an Event Sourced aggregate that is created by the given
     * <code>aggregateFactory</code>. The given <code>decorator</code> is used to decorate event streams.
     * <p/>
     * The repository returned must be used by Command Handlers subscribed to this Command Bus for loading aggregate
     * instances. Using any other repository instance may result in undefined outcome (a.k.a. concurrency problems).
     * <p/>
     * Note that a second invocation of this method with an aggregate factory for the same aggregate type <em>may</em>
     * return the same instance as the first invocation, even if the given <code>decorator</code> is different.
     *
     * @param aggregateFactory The factory creating uninitialized instances of the Aggregate
     * @param decorator        The decorator to decorate events streams with
     * @param <T>              The type of aggregate to create the repository for
     * @return the repository that provides access to stored aggregates
     */
    public <T extends EventSourcedAggregateRoot> Repository<T> createRepository(AggregateFactory<T> aggregateFactory,
                                                                                EventStreamDecorator decorator) {
        for (CommandHandlerInvoker invoker : commandHandlerInvokers) {
            invoker.createRepository(aggregateFactory, decorator);
        }
        return new DisruptorRepository<T>(aggregateFactory.getTypeIdentifier());
    }

    @Override
    public <C> void subscribe(String commandName, CommandHandler<? super C> handler) {
        commandHandlers.put(commandName, handler);
    }

    @Override
    public <C> boolean unsubscribe(String commandName, CommandHandler<? super C> handler) {
        return commandHandlers.remove(commandName, handler);
    }

    /**
     * Shuts down the command bus. It no longer accepts new commands, and finishes processing commands that have
     * already been published. This method will not shut down any executor that has been provided as part of the
     * Configuration.
     */
    public void stop() {
        if (!started) {
            return;
        }
        started = false;
        long lastChangeDetected = System.currentTimeMillis();
        long lastKnownCursor = disruptor.getRingBuffer().getCursor();
        while (System.currentTimeMillis() - lastChangeDetected < coolingDownPeriod && !Thread.interrupted()) {
            if (disruptor.getRingBuffer().getCursor() != lastKnownCursor) {
                lastChangeDetected = System.currentTimeMillis();
                lastKnownCursor = disruptor.getRingBuffer().getCursor();
            }
        }
        disruptorShutDown = true;
        disruptor.shutdown();
        if (executorService != null) {
            executorService.shutdown();
        }
    }

    private static class FailureLoggingCommandCallback implements CommandCallback<Object> {

        @Override
        public void onSuccess(Object result) {
        }

        @Override
        public void onFailure(Throwable cause) {
            logger.info("An error occurred while handling a command.", cause);
        }
    }

    private class ExceptionHandler implements com.lmax.disruptor.ExceptionHandler {

        @Override
        public void handleEventException(Throwable ex, long sequence, Object event) {
            logger.error("Exception occurred while processing a {}.",
                         ((CommandHandlingEntry) event).getCommand().getPayloadType().getSimpleName(),
                         ex);
        }

        @Override
        public void handleOnStartException(Throwable ex) {
            logger.error("Failed to start the DisruptorCommandBus.", ex);
            disruptor.shutdown();
        }

        @Override
        public void handleOnShutdownException(Throwable ex) {
            logger.error("Error while shutting down the DisruptorCommandBus", ex);
        }
    }

    private static class DisruptorRepository<T extends EventSourcedAggregateRoot> implements Repository<T> {

        private final String typeIdentifier;

        public DisruptorRepository(String typeIdentifier) {
            this.typeIdentifier = typeIdentifier;
        }

        @SuppressWarnings("unchecked")
        @Override
        public T load(Object aggregateIdentifier, Long expectedVersion) {
            return (T) CommandHandlerInvoker.getRepository(typeIdentifier).load(aggregateIdentifier, expectedVersion);
        }

        @SuppressWarnings("unchecked")
        @Override
        public T load(Object aggregateIdentifier) {
            return (T) CommandHandlerInvoker.getRepository(typeIdentifier).load(aggregateIdentifier);
        }

        @Override
        public void add(T aggregate) {
            CommandHandlerInvoker.getRepository(typeIdentifier).add(aggregate);
        }
    }

    private static class NoOpEventStreamDecorator implements EventStreamDecorator {

        public static final EventStreamDecorator INSTANCE = new NoOpEventStreamDecorator();

        @Override
        public DomainEventStream decorateForRead(String aggregateType, Object aggregateIdentifier,
                                                 DomainEventStream eventStream) {
            return eventStream;
        }

        @Override
        public DomainEventStream decorateForAppend(String aggregateType, EventSourcedAggregateRoot aggregate,
                                                   DomainEventStream eventStream) {
            return eventStream;
        }
    }
}
TOP

Related Classes of org.axonframework.commandhandling.disruptor.DisruptorCommandBus$ExceptionHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.