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

pro.jk.ejoker.eventing.impl.DefaultEventCommittingService Maven / Gradle / Ivy

package pro.jk.ejoker.eventing.impl;

import static pro.jk.ejoker.common.system.extension.LangUtil.await;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pro.jk.ejoker.EJokerEnvironment;
import pro.jk.ejoker.commanding.CommandResult;
import pro.jk.ejoker.commanding.CommandStatus;
import pro.jk.ejoker.commanding.ICommand;
import pro.jk.ejoker.commanding.ProcessingCommand;
import pro.jk.ejoker.commanding.ProcessingCommandMailbox;
import pro.jk.ejoker.common.context.annotation.context.Dependence;
import pro.jk.ejoker.common.context.annotation.context.EInitialize;
import pro.jk.ejoker.common.context.annotation.context.EService;
import pro.jk.ejoker.common.service.IJSONConverter;
import pro.jk.ejoker.common.system.enhance.EachUtilx;
import pro.jk.ejoker.common.system.enhance.MapUtilx;
import pro.jk.ejoker.common.system.enhance.StringUtilx;
import pro.jk.ejoker.common.system.extension.acrossSupport.EJokerFutureUtil;
import pro.jk.ejoker.common.system.task.context.SystemAsyncHelper;
import pro.jk.ejoker.common.system.task.io.IOHelper;
import pro.jk.ejoker.domain.IAggregateRootFactory;
import pro.jk.ejoker.domain.IAggregateStorage;
import pro.jk.ejoker.domain.IMemoryCache;
import pro.jk.ejoker.eventing.DomainEventStream;
import pro.jk.ejoker.eventing.DomainEventStreamMessage;
import pro.jk.ejoker.eventing.EventCommittingContext;
import pro.jk.ejoker.eventing.EventCommittingContextMailBox;
import pro.jk.ejoker.eventing.IEventCommittingService;
import pro.jk.ejoker.eventing.IEventStore;
import pro.jk.ejoker.messaging.IMessagePublisher;

@EService
public class DefaultEventCommittingService implements IEventCommittingService {

	private final static Logger logger = LoggerFactory.getLogger(DefaultEventCommittingService.class);

	private final int eventMailboxCount = EJokerEnvironment.EVENT_MAILBOX_ACTOR_COUNT;
	
	@Dependence
	private IJSONConverter jsonSerializer;
	
	@Dependence
	private IMemoryCache memoryCache;
	
	@Dependence
	private IAggregateRootFactory aggregateRootFactory;
	
	@Dependence
	private IAggregateStorage aggregateStorage;
	
	@Dependence
	private IEventStore eventStore;
	
	@Dependence
	private IMessagePublisher domainEventPublisher;
	
	@Dependence
	private IOHelper ioHelper;

	@Dependence
	private SystemAsyncHelper systemAsyncHelper;
	
	private final List eventCommittingContextMailBoxList = new ArrayList<>();

	@EInitialize
	private void init() {
		
		for(int i=0; i committingContexts) {
		
		if (null == committingContexts || committingContexts.isEmpty())
			return;
		
		LinkedList domainEventStreams = new LinkedList<>();
		EachUtilx.forEach(committingContexts, item -> domainEventStreams.add(item.getEventStream()));
		
		ioHelper.tryAsyncAction2(
				"BatchPersistEventAsync",
				() -> eventStore.batchAppendAsync(domainEventStreams),
				appendResult -> {
					
					EventCommittingContextMailBox eventMailBox = committingContexts.get(0).getMailBox();
					
					if(null == appendResult) {
						logger.error("BatchPersistAggregateEvents result is null, the current event committing mailbox should be pending. [mailboxNumber: {}]",
								eventMailBox.getNumber());
						return;
					}

					Map> groupCommittedContextDict = new HashMap<>();
			        for(EventCommittingContext ecc : committingContexts) {
			        	MapUtilx
			        		.getOrAdd(groupCommittedContextDict, ecc.getEventStream().getAggregateRootId(), () -> new ArrayList<>())
			        		.add(ecc);
			        }
					
					Set successIds = appendResult.getSuccessAggregateRootIdList();
					if(null != successIds && !successIds.isEmpty()) {
						// 针对持久化成功的聚合根,发布这些聚合根的事件到Q端
				        EachUtilx.forEach(successIds, aggregateRootId -> {
							List committingContextList = groupCommittedContextDict.get(aggregateRootId);
							if(null != committingContextList && !committingContextList.isEmpty()) {
								if (logger.isDebugEnabled()) {
									logger.debug(
											"BatchPersistAggregateEvents succeed. [mailboxNumber: {}, aggregateRootId: {}, events: {}]",
											eventMailBox.getNumber(),
		                                    aggregateRootId,
		                                    committingContextList
		                                    	.stream()
		                                    	.map(EventCommittingContext::getEventStream)
		                                    	.map(DomainEventStream::toString)
		                                    	.reduce((x, y) -> x + ", " + y)
		                                    	.get()
		                                    );
								}
								for(EventCommittingContext ecc : committingContextList) {
				        			publishDomainEventAsync(ecc.getProcessingCommand(), ecc.getEventStream());
				        		}
							}
						});
						
					}
					
					Map> duplicateCommandAggregateRootIdList = appendResult.getDuplicateCommandAggregateRootIdList();
		                //针对持久化出现重复的命令ID,则重新发布这些命令对应的领域事件到Q端
						EachUtilx.forEach(duplicateCommandAggregateRootIdList, (aggregateRootId, duplicateCommandIdList) -> {
							List contextList = groupCommittedContextDict.get(aggregateRootId);
							if(null == contextList || contextList.isEmpty()) return;
							EventCommittingContext committingContext = contextList.get(0);
							
							logger.warn("BatchPersistAggregateEvents has duplicate commandIds. [mailboxNumber: {}, aggregateRootId: {}, commandIds: {}]",
	                                eventMailBox.getNumber(), aggregateRootId, duplicateCommandIdList);

							if(1l == committingContext.getEventStream().getVersion()) {
								await(handleFirstEventDuplicationAsync(committingContext));
							} else {
								await(resetCommandMailBoxConsumingSequence(committingContext, committingContext.getProcessingCommand().getSequence(), duplicateCommandIdList));
							}
						});
					
					Set duplicateAggrIdList = appendResult.getDuplicateEventAggregateRootIdList();
					if(null != duplicateAggrIdList && !duplicateAggrIdList.isEmpty()) {
		                //针对持久化出现版本冲突的聚合根,则自动处理每个聚合根的冲突
						for(String aggregateRootId : duplicateAggrIdList) {
							Optional committingContextOp = committingContexts
									.stream()
									.filter(x -> aggregateRootId.equals(x.getEventStream().getAggregateRootId()))
									.findFirst();
							if(committingContextOp.isPresent()) {
								EventCommittingContext eventCommittingContext = committingContextOp.get();
								logger.warn(
										"BatchPersistAggregateEvents has version confliction. [mailboxNumber: {}, aggregateRootId: {}, conflictVersion: {}]",
										eventMailBox.getNumber(),
										aggregateRootId,
										eventCommittingContext.getEventStream().getVersion()
										);

								if(1l == eventCommittingContext.getEventStream().getVersion()) {
									await(handleFirstEventDuplicationAsync(eventCommittingContext));
								} else {
									await(resetCommandMailBoxConsumingSequence(eventCommittingContext, eventCommittingContext.getProcessingCommand().getSequence()));
								}
								
							}
						}
					}
					
					eventMailBox.finishRun();
					
				},
				() -> StringUtilx.fmt("[contextListCount: {}]", committingContexts.size()),
				true
			);
	}

	/**
	 * * 这里是由CompletableFuture提供的任务链功能
	 * @param context
	 * @param consumingSequence
	 * @return
	 */
	private CompletableFuture resetCommandMailBoxConsumingSequence(EventCommittingContext context, long consumingSequence, List duplicateCommandIdList) {

//		final EventMailBox eventMailBox = context.eventMailBox;
//		IMailBox eventMailBox = context.getMailBox();
		final ProcessingCommand processingCommand = context.getProcessingCommand();
		final ICommand command = processingCommand.getMessage();
		final ProcessingCommandMailbox commandMailBox = processingCommand.getMailBox();
		final EventCommittingContextMailBox eventMailBox = context.getMailBox();
		final String aggregateRootId = context.getEventStream().getAggregateRootId();
		
		// 设置暂停标识,不排斥当前运行的任务,但是拒绝新的任务进入
		commandMailBox.pauseOnly();

		// commandMailBox.pause() 与 commandMailBox.resume() 并不在成对的try-finally过程中
		// 会不会出问题?
		// #fix 把pause过程拆解为pauseOnly 和 waitAcquireOnProcessing两个过程 更符合java
		return CompletableFuture.supplyAsync(() -> {

			// 等待完全的pause状态
			commandMailBox.acquireOnRunning();
			
			try {
				
				eventMailBox.removeAggregateAllEventCommittingContexts(aggregateRootId);
				// TODO @await
				await(memoryCache.refreshAggregateFromEventStoreAsync(context.getEventStream().getAggregateRootTypeName(), aggregateRootId));
				if(null != duplicateCommandIdList) {
					for(String commandIdDuplicated : duplicateCommandIdList) {
						commandMailBox.addDuplicateCommandId(commandIdDuplicated);
					}
				}
				commandMailBox.resetConsumingSequence(consumingSequence);
				
			} catch (RuntimeException ex) {
				logger.error("ResetCommandMailBoxConsumingOffset has unknown exception!!! [aggregateRootId: {}]",
						command.getAggregateRootId(), ex);
			} finally {
				commandMailBox.resume();
				commandMailBox.tryRun();
			}
			
			return null;
		});

	}

	private CompletableFuture resetCommandMailBoxConsumingSequence(EventCommittingContext context, long consumingSequence) {
		return resetCommandMailBoxConsumingSequence(context, consumingSequence, null);
	}
	
	private void tryToRepublishEventAsync(EventCommittingContext context) {

        ICommand command = context.getProcessingCommand().getMessage();
		
        ioHelper.tryAsyncAction2(
        		"FindEventByCommandIdAsync",
        		() -> eventStore.findAsync(command.getAggregateRootId(), command.getId()),
        		existingEventStream -> {
        			if (null != existingEventStream) {
        				
                        //这里,我们需要再重新做一遍发布事件这个操作;
                        //之所以要这样做是因为虽然该command产生的事件已经持久化成功,但并不表示事件已经发布出去了;
                        //因为有可能事件持久化成功了,但那时正好机器断电了,则发布事件都没有做;
                        publishDomainEventAsync(context.getProcessingCommand(), existingEventStream);
                        
                    } else {
                    	
                        //到这里,说明当前command想添加到eventStore中时,提示command重复,但是尝试从eventStore中取出该command时却找不到该command。
                        //出现这种情况,我们就无法再做后续处理了,这种错误理论上不会出现,除非eventStore的Add接口和Get接口出现读写不一致的情况;
                        //框架会记录错误日志,让开发者排查具体是什么问题。
                    	String errorMessage = StringUtilx.fmt(
                    			"Command should be exist in the event store, but we cannot find it from the event store, this should not be happen, and we just complete the command!!! [commandType: {}, commandId: {}, aggregateRootId: {}]",
	                            command.getClass().getName(),
	                            command.getId(),
	                            command.getAggregateRootId());
                        logger.error(errorMessage);
                        CommandResult commandResult = new CommandResult(CommandStatus.Failed,
                        		command.getId(),
                        		command.getAggregateRootId(),
                        		"Command should be exist in the event store, but we cannot find it from the event store.",
                        		String.class.getName());
                        finishCommandAsync(context.getProcessingCommand(), commandResult);
                        
                    }
        		},
        		() -> StringUtilx.fmt("[aggregateRootId: {}, commandId: {}]", command.getAggregateRootId(), command.getId()),
        		true
        		);
	}

	/**
	 * 遇到Version为1的事件的重复的时候,做特殊处理。
	 * @param context
	 */
	private Future handleFirstEventDuplicationAsync(final EventCommittingContext context) {
		
		DomainEventStream eventStream = context.getEventStream();

        ioHelper.tryAsyncAction2(
        		"FindFirstEventByVersion",
        		() -> eventStore.findAsync(eventStream.getAggregateRootId(), 1),
        		firstEventStream -> {

    				String commandId = context.getProcessingCommand().getMessage().getId();
    				if(null != firstEventStream) {
    					//判断是否是同一个command,如果是,则再重新做一遍发布事件;
                        //之所以要这样做,是因为虽然该command产生的事件已经持久化成功,但并不表示事件也已经发布出去了;
                        //有可能事件持久化成功了,但那时正好机器断电了,则发布事件都没有做;
    					if(commandId.equals(firstEventStream.getCommandId())) {
    						
							resetCommandMailBoxConsumingSequence(context,
									context.getProcessingCommand().getSequence() + 1).thenAcceptAsync(t -> {
										publishDomainEventAsync(context.getProcessingCommand(), firstEventStream);
									});
    						
    					} else {

                            //如果不是同一个command,则认为是两个不同的command重复创建ID相同的聚合根,我们需要记录错误日志,然后通知当前command的处理完成;
							logger.error(
									"Duplicate aggregate creation. [currentCommandId: {}, existingCommandId: {}, aggregateRootId: {}, aggregateRootTypeName: {}]",
									commandId,
									firstEventStream.getCommandId(),
									firstEventStream.getAggregateRootId(),
									firstEventStream.getAggregateRootTypeName());

							resetCommandMailBoxConsumingSequence(context, context.getProcessingCommand().getSequence() + 1)
									.thenAcceptAsync(t -> {
										CommandResult commandResult = new CommandResult(CommandStatus.Failed, commandId,
												eventStream.getAggregateRootId(), "Duplicate aggregate creation.",
												String.class.getName());
										finishCommandAsync(context.getProcessingCommand(), commandResult);
									});
                            
    					}
    				} else {
    					
						logger.error("Duplicate aggregate creation, but we cannot find the existing eventstream from eventstore, this should not be happen, and we just complete the command!!! [commandId: {}, aggregateRootId: {}, aggregateRootTypeName: {}]",
    	                        eventStream.getCommandId(),
    	                        eventStream.getAggregateRootId(),
    	                        eventStream.getAggregateRootTypeName());

						resetCommandMailBoxConsumingSequence(context, context.getProcessingCommand().getSequence() + 1)
								.thenApplyAsync(t -> {
									CommandResult commandResult = new CommandResult(CommandStatus.Failed, commandId,
											eventStream.getAggregateRootId(),
											"Duplicate aggregate creation, but we cannot find the existing eventstream from eventstore.",
											String.class.getName());
									finishCommandAsync(context.getProcessingCommand(), commandResult);
									return null;
								});
    				}
        		},
        		() -> StringUtilx.fmt("[eventStream: {}]", jsonSerializer.convert(eventStream)),
        		true
        		);

		return EJokerFutureUtil.completeFuture();
	}

	private void publishDomainEventAsync(ProcessingCommand processingCommand, DomainEventStreamMessage eventStream) {

		ioHelper.tryAsyncAction2(
				"PublishEventAsync",
				() -> domainEventPublisher.publishAsync(eventStream),
				r -> {
					
					logger.debug("Publish event success. [eventStream: {}]", eventStream.toString());

					String commandHandleResult = processingCommand.getCommandExecuteContext().getResult();
					CommandResult commandResult = new CommandResult(
							CommandStatus.Success,
							processingCommand.getMessage().getId(),
							eventStream.getAggregateRootId(),
							commandHandleResult,
							String.class.getName());
					finishCommandAsync(processingCommand, commandResult);
					},
				() -> StringUtilx.fmt("[eventStream: {}]", eventStream.toString()),
				true
				);
	}

	private Future finishCommandAsync(ProcessingCommand processingCommand, CommandResult commandResult) {
		return processingCommand.getMailBox().finishMessage(processingCommand, commandResult);
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy