org.enodeframework.eventing.impl.DefaultEventCommittingService.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of enode Show documentation
Show all versions of enode Show documentation
The enodeframework core implementation.
package org.enodeframework.eventing.impl
import org.enodeframework.commanding.CommandResult
import org.enodeframework.commanding.CommandStatus
import org.enodeframework.commanding.ProcessingCommand
import org.enodeframework.common.exception.MailBoxInvalidException
import org.enodeframework.common.io.IOHelper
import org.enodeframework.common.serializing.ISerializeService
import org.enodeframework.domain.IAggregateRoot
import org.enodeframework.domain.IMemoryCache
import org.enodeframework.eventing.*
import org.enodeframework.messaging.IMessagePublisher
import org.slf4j.LoggerFactory
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor
import java.util.stream.Collectors
/**
* @author [email protected]
*/
class DefaultEventCommittingService(private val memoryCache: IMemoryCache, private val eventStore: IEventStore, private val serializeService: ISerializeService, private val domainEventPublisher: IMessagePublisher, private val eventMailBoxCount: Int, executor: Executor?) : IEventCommittingService {
private val eventCommittingContextMailBoxList: MutableList
constructor(memoryCache: IMemoryCache, eventStore: IEventStore, serializeService: ISerializeService, domainEventPublisher: IMessagePublisher, executor: Executor?) : this(memoryCache, eventStore, serializeService, domainEventPublisher, 4, executor)
override fun commitDomainEventAsync(eventCommittingContext: EventCommittingContext) {
val eventMailboxIndex = getEventMailBoxIndex(eventCommittingContext.eventStream.aggregateRootId)
val eventMailbox = eventCommittingContextMailBoxList[eventMailboxIndex]
eventMailbox.enqueueMessage(eventCommittingContext)
}
override fun publishDomainEventAsync(processingCommand: ProcessingCommand, eventStream: DomainEventStream): CompletableFuture {
if (eventStream.items == null || eventStream.items.size == 0) {
eventStream.items = processingCommand.items
}
val eventStreamMessage = DomainEventStreamMessage(
processingCommand.message.id,
eventStream.aggregateRootId,
eventStream.version,
eventStream.aggregateRootTypeName,
eventStream.events(),
eventStream.items)
return publishDomainEventAsync(processingCommand, eventStreamMessage, 0)
}
private fun getEventMailBoxIndex(aggregateRootId: String): Int {
var hash = 23
for (c in aggregateRootId.toCharArray()) {
hash = (hash shl 5) - hash + c.toInt()
}
if (hash < 0) {
hash = Math.abs(hash)
}
return hash % eventMailBoxCount
}
private fun batchPersistEventAsync(committingContexts: List, retryTimes: Int) {
if (committingContexts.isEmpty()) {
return
}
IOHelper.tryAsyncActionRecursively("BatchPersistEventAsync",
{ eventStore.batchAppendAsync(committingContexts.stream().map { obj: EventCommittingContext -> obj.eventStream }.collect(Collectors.toList())) },
{ result: EventAppendResult? ->
val eventMailBox = committingContexts.stream()
.findFirst()
.orElseThrow { MailBoxInvalidException("eventMailBox can not be null") }
.mailBox
if (result == null) {
logger.error("Batch persist events success, but the persist result is null, the current event committing mailbox should be pending, mailboxNumber: {}", eventMailBox.number)
return@tryAsyncActionRecursively
}
val appendContextList = ArrayList()
//针对持久化成功的聚合根,正常发布这些聚合根的事件到Q端
if (result.successAggregateRootIdList.size > 0) {
for (aggregateRootId in result.successAggregateRootIdList) {
committingContexts.stream().filter { x: EventCommittingContext -> x.eventStream.aggregateRootId == aggregateRootId }.forEach { eventCommittingContext ->
val context = EventAppendContext()
context.success = true
context.duplicateCommandIdList = ArrayList()
context.committingContext = eventCommittingContext
appendContextList.add(context)
}
}
if (logger.isDebugEnabled) {
logger.debug("Batch persist events success, mailboxNumber: {}, result: {}", eventMailBox.number, serializeService.serialize(result.successAggregateRootIdList))
}
}
//针对持久化出现重复的命令ID,在命令MailBox中标记为已重复,在事件MailBox中清除对应聚合根产生的事件,且重新发布这些命令对应的领域事件到Q端
if (result.duplicateCommandAggregateRootIdList.size > 0) {
for ((key, value) in result.duplicateCommandAggregateRootIdList) {
committingContexts.stream().filter { x: EventCommittingContext -> key == x.eventStream.aggregateRootId }.findFirst().ifPresent { eventCommittingContext: EventCommittingContext ->
val context = EventAppendContext()
context.duplicateCommandIdList = value
context.committingContext = eventCommittingContext
appendContextList.add(context)
}
}
logger.warn("Batch persist events has duplicate commandIds, mailboxNumber: {}, result: {}", eventMailBox.number, serializeService.serialize(result.duplicateCommandAggregateRootIdList))
}
//针对持久化出现版本冲突的聚合根,则自动处理每个聚合根的冲突
if (result.duplicateEventAggregateRootIdList.size > 0) {
for (aggregateRootId in result.duplicateEventAggregateRootIdList) {
committingContexts.stream().filter { x: EventCommittingContext -> x.eventStream.aggregateRootId == aggregateRootId }.findFirst().ifPresent { eventCommittingContext ->
val context = EventAppendContext()
context.duplicateCommandIdList = ArrayList()
context.committingContext = eventCommittingContext
appendContextList.add(context)
}
}
logger.warn("Batch persist events duplicated, mailboxNumber: {}, result: {}", eventMailBox.number, serializeService.serialize(result.duplicateEventAggregateRootIdList))
}
processDuplicateAggregateRootRecursively(0, appendContextList, eventMailBox)
//最终将当前的EventMailBox的本次处理标记为处理完成,然后继续可以处理下一批事件
},
{ String.format("[contextListCount:%d]", committingContexts.size) },
null, retryTimes, true)
}
class EventAppendContext {
lateinit var committingContext: EventCommittingContext
var duplicateCommandIdList: List = ArrayList()
var success: Boolean = false
}
private fun processDuplicateAggregateRootRecursively(index: Int, contexts: List, eventMailBox: EventCommittingContextMailBox) {
if (contexts.isEmpty()) {
return
}
if (index == contexts.size) {
eventMailBox.completeRun()
return
}
val context = contexts.get(index);
val eventCommittingContext = context.committingContext
val duplicateCommandIdList = context.duplicateCommandIdList
if (context.success) {
publishDomainEventAsync(eventCommittingContext.processingCommand, eventCommittingContext.eventStream).thenAccept {
processDuplicateAggregateRootRecursively(index + 1, contexts, eventMailBox)
}
return
}
if (eventCommittingContext.eventStream.version == 1) {
handleFirstEventDuplicationAsync(eventCommittingContext, 0).thenAccept { processDuplicateAggregateRootRecursively(index + 1, contexts, eventMailBox) }
} else {
resetCommandMailBoxConsumingSequence(eventCommittingContext, eventCommittingContext.processingCommand.sequence, duplicateCommandIdList).thenAccept { processDuplicateAggregateRootRecursively(index + 1, contexts, eventMailBox) }
}
}
private fun resetCommandMailBoxConsumingSequence(context: EventCommittingContext, consumingSequence: Long, duplicateCommandIdList: List?): CompletableFuture {
val commandMailBox = context.processingCommand.mailBox
val eventMailBox = context.mailBox
val aggregateRootId = context.eventStream.aggregateRootId
commandMailBox.pause()
eventMailBox.removeAggregateAllEventCommittingContexts(aggregateRootId)
return memoryCache.refreshAggregateFromEventStoreAsync(context.eventStream.aggregateRootTypeName, aggregateRootId).thenApply { x: IAggregateRoot? ->
try {
if (duplicateCommandIdList != null) {
for (commandId in duplicateCommandIdList) {
commandMailBox.addDuplicateCommandId(commandId)
}
}
commandMailBox.resetConsumingSequence(consumingSequence)
} finally {
commandMailBox.resume()
commandMailBox.tryRun()
}
true
}.exceptionally { ex: Throwable? ->
logger.error("ResetCommandMailBoxConsumingSequence has unknown exception, aggregateRootId: {}", aggregateRootId, ex)
null
}
}
private fun tryToRepublishEventAsync(context: EventCommittingContext, retryTimes: Int) {
val command = context.processingCommand.message
IOHelper.tryAsyncActionRecursively("FindEventByCommandIdAsync",
{ eventStore.findAsync(context.eventStream.aggregateRootId, command.id) },
{ result: DomainEventStream? ->
if (result != null) {
//这里,我们需要再重新做一遍发布事件这个操作;
//之所以要这样做是因为虽然该command产生的事件已经持久化成功,但并不表示事件已经发布出去了;
//因为有可能事件持久化成功了,但那时正好机器断电了,则发布事件都没有做;
publishDomainEventAsync(context.processingCommand, result)
} else {
//到这里,说明当前command想添加到eventStore中时,提示command重复,但是尝试从eventStore中取出该command时却找不到该command。
//出现这种情况,我们就无法再做后续处理了,这种错误理论上不会出现,除非eventStore的Add接口和Get接口出现读写不一致的情况;
//框架会记录错误日志,让开发者排查具体是什么问题。
val errorMessage = String.format("Command should be exist in the event store, but we cannot find it from the event store, this should not be happen, and we cannot continue again. commandType:%s, commandId:%s, aggregateRootId:%s",
command.javaClass.name,
command.id,
context.eventStream.aggregateRootId)
logger.error(errorMessage)
val commandResult = CommandResult(CommandStatus.Failed, command.id, command.aggregateRootId, "Command should be exist in the event store, but we cannot find it from the event store.", String::class.java.name)
completeCommand(context.processingCommand, commandResult)
}
},
{ String.format("[aggregateRootId:%s, commandId:%s]", command.aggregateRootId, command.id) },
null,
retryTimes, true)
}
private fun handleFirstEventDuplicationAsync(context: EventCommittingContext, retryTimes: Int): CompletableFuture {
val future = CompletableFuture()
IOHelper.tryAsyncActionRecursively("FindFirstEventByVersion",
{ eventStore.findAsync(context.eventStream.aggregateRootId, 1) },
{ result: DomainEventStream? ->
if (result != null) {
//判断是否是同一个command,如果是,则再重新做一遍发布事件;
//之所以要这样做,是因为虽然该command产生的事件已经持久化成功,但并不表示事件也已经发布出去了;
//有可能事件持久化成功了,但那时正好机器断电了,则发布事件都没有做;
if (context.processingCommand.message.id == result.commandId) {
resetCommandMailBoxConsumingSequence(context, context.processingCommand.sequence + 1, null)
.thenAccept {
publishDomainEventAsync(context.processingCommand, result).thenAccept { future.complete(true) }
}
} else {
//如果不是同一个command,则认为是两个不同的command重复创建ID相同的聚合根,我们需要记录错误日志,然后通知当前command的处理完成;
val errorMessage = String.format("Duplicate aggregate creation. current commandId:%s, existing commandId:%s, aggregateRootId:%s, aggregateRootTypeName:%s",
context.processingCommand.message.id,
result.commandId,
result.aggregateRootId,
result.aggregateRootTypeName)
logger.error(errorMessage)
resetCommandMailBoxConsumingSequence(context, context.processingCommand.sequence + 1, null)
.thenAccept {
val commandResult = CommandResult(CommandStatus.Failed, context.processingCommand.message.id, context.eventStream.aggregateRootId, "Duplicate aggregate creation.", String::class.java.name)
completeCommand(context.processingCommand, commandResult)
.thenAccept { future.complete(true) }
}
}
} else {
val errorMessage = String.format("Duplicate aggregate creation, but we cannot find the existing eventstream from eventstore. commandId:%s, aggregateRootId:%s, aggregateRootTypeName:%s",
context.eventStream.commandId,
context.eventStream.aggregateRootId,
context.eventStream.aggregateRootTypeName)
logger.error(errorMessage)
resetCommandMailBoxConsumingSequence(context, context.processingCommand.sequence + 1, null).thenAccept {
val commandResult = CommandResult(CommandStatus.Failed, context.processingCommand.message.id, context.eventStream.aggregateRootId, "Duplicate aggregate creation, but we cannot find the existing eventstream from eventstore.", String::class.java.name)
completeCommand(context.processingCommand, commandResult)
.thenAccept { future.complete(true) }
}
}
},
{ String.format("[eventStream:%s]", serializeService.serialize(context.eventStream)) },
null, retryTimes, true)
return future
}
private fun publishDomainEventAsync(processingCommand: ProcessingCommand, eventStream: DomainEventStreamMessage, retryTimes: Int): CompletableFuture {
val future = CompletableFuture()
IOHelper.tryAsyncActionRecursivelyWithoutResult("PublishDomainEventAsync",
{ domainEventPublisher.publishAsync(eventStream) },
{
if (logger.isDebugEnabled) {
logger.debug("Publish domain events success, {}", serializeService.serialize(eventStream))
}
val commandHandleResult = processingCommand.commandExecuteContext.result
val commandResult = CommandResult(CommandStatus.Success, processingCommand.message.id, eventStream.getAggregateRootId(), commandHandleResult, String::class.java.name)
completeCommand(processingCommand, commandResult).thenAccept { future.complete(true) }
},
{ String.format("[eventStream:%s]", serializeService.serialize(eventStream)) },
null, retryTimes, true)
return future
}
private fun completeCommand(processingCommand: ProcessingCommand, commandResult: CommandResult): CompletableFuture {
return processingCommand.mailBox.completeMessage(processingCommand, commandResult)
}
companion object {
private val logger = LoggerFactory.getLogger(DefaultEventCommittingService::class.java)
}
init {
eventCommittingContextMailBoxList = ArrayList()
for (i in 0 until eventMailBoxCount) {
val mailBox = EventCommittingContextMailBox(i, 1000, { x: List -> batchPersistEventAsync(x, 0) }, executor)
eventCommittingContextMailBoxList.add(mailBox)
}
}
}