public Task<AsyncTaskResult<EventAppendResult>> AppendAsync(DomainEventStream eventStream) { var record = ConvertTo(eventStream); return _ioHelper.TryIOFuncAsync<AsyncTaskResult<EventAppendResult>>(async () => { try { using (var connection = GetConnection()) { await connection.InsertAsync(record, _eventTable); return new AsyncTaskResult<EventAppendResult>(AsyncTaskStatus.Success, EventAppendResult.Success); } } catch (SqlException ex) { if (ex.Number == 2627 && ex.Message.Contains(_primaryKeyName)) { return new AsyncTaskResult<EventAppendResult>(AsyncTaskStatus.Success, EventAppendResult.DuplicateEvent); } else if (ex.Number == 2601 && ex.Message.Contains(_commandIndexName)) { return new AsyncTaskResult<EventAppendResult>(AsyncTaskStatus.Success, EventAppendResult.DuplicateCommand); } _logger.Error(string.Format("Append event has sql exception, eventStream: {0}", eventStream), ex); return new AsyncTaskResult<EventAppendResult>(AsyncTaskStatus.IOException, ex.Message, EventAppendResult.Failed); } catch (Exception ex) { _logger.Error(string.Format("Append event has unknown exception, eventStream: {0}", eventStream), ex); return new AsyncTaskResult<EventAppendResult>(AsyncTaskStatus.Failed, ex.Message, EventAppendResult.Failed); } }, "AppendEventsAsync"); }
public EventAppendResult Append(DomainEventStream eventStream) { var record = ConvertTo(eventStream); return _ioHelper.TryIOFunc(() => { using (var connection = GetConnection()) { try { connection.Insert(record, _eventTable); return EventAppendResult.Success; } catch (SqlException ex) { if (ex.Number == 2627) { if (ex.Message.Contains(_primaryKeyName)) { return EventAppendResult.DuplicateEvent; } } throw; } } }, "AppendEvents"); }
private EventAppendResult Append(DomainEventStream eventStream) { var aggregateInfo = _aggregateInfoDict.GetOrAdd(eventStream.AggregateRootId, x => new AggregateInfo()); var originalStatus = Interlocked.CompareExchange(ref aggregateInfo.Status, Editing, UnEditing); if (originalStatus == aggregateInfo.Status) { return EventAppendResult.DuplicateEvent; } try { if (eventStream.Version == aggregateInfo.CurrentVersion + 1) { aggregateInfo.EventDict[eventStream.Version] = eventStream; aggregateInfo.CommandDict[eventStream.CommandId] = eventStream; aggregateInfo.CurrentVersion = eventStream.Version; return EventAppendResult.Success; } return EventAppendResult.DuplicateEvent; } finally { Interlocked.Exchange(ref aggregateInfo.Status, UnEditing); } }
public void PublishDomainEventAsync(ProcessingCommand processingCommand, DomainEventStream eventStream) { if (eventStream.Items == null || eventStream.Items.Count == 0) { eventStream.Items = processingCommand.Items; } var eventStreamMessage = new DomainEventStreamMessage(processingCommand.Message.Id, eventStream.AggregateRootId, eventStream.Version, eventStream.AggregateRootTypeName, eventStream.Events, eventStream.Items); PublishDomainEventAsync(processingCommand, eventStreamMessage, 0); }
private void AddDataRow(DataTable table, DomainEventStream eventStream) { var row = table.NewRow(); row["AggregateRootId"] = eventStream.AggregateRootId; row["AggregateRootTypeName"] = eventStream.AggregateRootTypeName; row["CommandId"] = eventStream.CommandId; row["Version"] = eventStream.Version; row["CreatedOn"] = eventStream.Timestamp; row["Events"] = _jsonSerializer.Serialize(_eventSerializer.Serialize(eventStream.Events)); table.Rows.Add(row); }
private StreamRecord ConvertTo(DomainEventStream eventStream) { return(new StreamRecord { CommandId = eventStream.CommandId, AggregateRootId = eventStream.AggregateRootId, AggregateRootTypeName = eventStream.AggregateRootTypeName, Version = eventStream.Version, CreatedOn = eventStream.Timestamp, Events = _jsonSerializer.Serialize(_eventSerializer.Serialize(eventStream.Events)) }); }
static void AppendAsyncTest() { var aggreagateRootId = ObjectId.GenerateNewStringId(); var count = int.Parse(ConfigurationManager.AppSettings["appendAsyncCount"]); var printSize = count / 10; var eventStore = ObjectContainer.Resolve <IEventStore>(); var createEventStream = new Func <int, DomainEventStream>(version => { var evnt = new TestEvent { AggregateRootId = aggreagateRootId, Version = version }; var eventStream = new DomainEventStream(ObjectId.GenerateNewStringId(), aggreagateRootId, "SampleAggregateRootTypeName", version, DateTime.Now, new IDomainEvent[] { evnt }); return(eventStream); }); Console.WriteLine("start to append test, totalCount:" + count); var current = 0; var watch = Stopwatch.StartNew(); var waitHandle = new ManualResetEvent(false); for (var i = 1; i <= count; i++) { eventStore.AppendAsync(createEventStream(i)).ContinueWith(t => { if (t.Result.Data == EventAppendResult.DuplicateEvent) { Console.WriteLine("duplicated event stream."); return; } else if (t.Result.Data == EventAppendResult.DuplicateCommand) { Console.WriteLine("duplicated command execution."); return; } var local = Interlocked.Increment(ref current); if (local % printSize == 0) { Console.WriteLine("appended {0}, time:{1}", local, watch.ElapsedMilliseconds); if (local == count) { Console.WriteLine("append throughput: {0} events/s", 1000 * local / watch.ElapsedMilliseconds); waitHandle.Set(); } } }); } waitHandle.WaitOne(); }
private void VerifyEvent(DomainEventStream eventStream) { var current = this as IAggregateRoot; if (eventStream.Version > 1 && eventStream.AggregateRootId != current.UniqueId) { throw new Exception(string.Format("Invalid domain event stream, aggregateRootId:{0}, expected aggregateRootId:{1}", eventStream.AggregateRootId, current.UniqueId)); } if (eventStream.Version != current.Version + 1) { throw new Exception(string.Format("Invalid domain event stream, version:{0}, expected version:{1}", eventStream.Version, current.Version)); } }
public void ShouldAppendDomainEventToEndOfStream() { TestAggregateRoot aggregateRoot = new TestAggregateRoot(Guid.NewGuid()); var stream = new DomainEventStream(aggregateRoot.Id); stream.Should().HaveCount(0); var aggregateRootDomainEvent = new AggregateRootChangedDomainEvent(aggregateRoot.Id, Guid.NewGuid()); IDomainEventStream result = stream.AppendDomainEvent(aggregateRootDomainEvent); result.Should().HaveCount(1); }
private void VerifyEvent(DomainEventStream eventStream) { var current = this as IAggregateRoot; if (eventStream.Version > 1 && eventStream.AggregateRootId != current.UniqueId) { throw new InvalidOperationException(string.Format("Invalid domain event stream, aggregateRootId:{0}, expected aggregateRootId:{1}, type:{2}", eventStream.AggregateRootId, current.UniqueId, current.GetType().FullName)); } if (eventStream.Version != current.Version + 1) { throw new InvalidOperationException(string.Format("Invalid domain event stream, version:{0}, expected version:{1}, current aggregateRoot type:{2}, id:{3}", eventStream.Version, current.Version, this.GetType().FullName, current.UniqueId)); } }
public void ShouldThrowIfAggregateRootIdDoesNotMatch() { TestAggregateRoot aggregateRoot1 = new TestAggregateRoot(Guid.NewGuid()); TestAggregateRoot aggregateRoot2 = new TestAggregateRoot(Guid.NewGuid()); var aggregateRoot1Stream = new DomainEventStream(aggregateRoot1.Id); aggregateRoot1Stream.Should().HaveCount(0); var aggregateRoot2DomainEvent = new AggregateRootChangedDomainEvent(aggregateRoot2.Id, Guid.NewGuid()); // Append domain event of aggregate 2 to stream of aggregate 1. aggregateRoot1Stream.Invoking(s => s.AppendDomainEvent(aggregateRoot2DomainEvent)).Should().Throw <InvalidOperationException>(); }
void IAggregateRoot.RestoreFromEvents(DomainEventStream eventStream) { if (eventStream == null) { return; } //VerifyEvent(domainEventStream);//Don't VerifyEvent when CompatibleAggregateInitStyle.RepositoryOnly or CompatibleAggregateInitStyle.RepositoryThenEventSourcing foreach (var domainEvent in eventStream.Events) { HandleEvent(domainEvent); } _version = eventStream.Version; }
public void ShouldBeEqualToNumberOfDomainEvents() { TestAggregateRoot aggregateRoot = new TestAggregateRoot(Guid.NewGuid()); // 3 domain events. var domainEvent1 = new AggregateRootChangedDomainEvent(aggregateRoot.Id, Guid.NewGuid()); var domainEvent2 = new AggregateRootChangedDomainEvent(aggregateRoot.Id, Guid.NewGuid()); var domainEvent3 = new AggregateRootChangedDomainEvent(aggregateRoot.Id, Guid.NewGuid()); DomainEventStream stream = new DomainEventStream(aggregateRoot.Id, new[] { domainEvent1, domainEvent2, domainEvent3 }); stream.DomainEventCount.Should().Be(3); }
static void Main(string[] args) { InitializeENodeFramework(); var aggreagateRootId = ObjectId.GenerateNewStringId(); var count = int.Parse(ConfigurationManager.AppSettings["count"]); var eventStore = ObjectContainer.Resolve <IEventStore>(); var watch = Stopwatch.StartNew(); var createEventStream = new Func <int, DomainEventStream>(version => { var evnt = new TestEvent { AggregateRootId = aggreagateRootId, Version = version }; var eventStream = new DomainEventStream(ObjectId.GenerateNewStringId(), aggreagateRootId, "SampleAggregateRootTypeName", version, DateTime.Now, new IDomainEvent[] { evnt }); return(eventStream); }); var current = 0; for (var i = 1; i <= count; i++) { eventStore.AppendAsync(createEventStream(i)).ContinueWith(t => { if (t.Result.Data == EventAppendResult.DuplicateEvent) { Console.WriteLine("duplicated event stream."); return; } else if (t.Result.Data == EventAppendResult.DuplicateCommand) { Console.WriteLine("duplicated command execution."); return; } var local = Interlocked.Increment(ref current); if (local % 1000 == 0) { Console.WriteLine("{0}, time:{1}", local, watch.ElapsedMilliseconds); } }); } Console.ReadLine(); }
private void RefreshAggregateMemoryCache(DomainEventStream aggregateFirstEventStream) { try { var aggregateRootType = _aggregateRootTypeCodeProvider.GetType(aggregateFirstEventStream.AggregateRootTypeCode); var aggregateRoot = _memoryCache.Get(aggregateFirstEventStream.AggregateRootId, aggregateRootType); if (aggregateRoot == null) { aggregateRoot = _aggregateRootFactory.CreateAggregateRoot(aggregateRootType); aggregateRoot.ReplayEvents(new DomainEventStream[] { aggregateFirstEventStream }); _memoryCache.Set(aggregateRoot); _logger.DebugFormat("Aggregate added into memory, commandId:{0}, aggregateRootType:{1}, aggregateRootId:{2}, aggregateRootVersion:{3}", aggregateFirstEventStream.CommandId, aggregateRootType.Name, aggregateRoot.UniqueId, aggregateRoot.Version); } } catch (Exception ex) { _logger.Error(string.Format("Refresh memory cache by aggregate first event stream failed, {0}", aggregateFirstEventStream), ex); } }
static void BatchAppendAsyncTest() { Console.WriteLine(""); var aggreagateRootId1 = ObjectId.GenerateNewStringId(); var aggreagateRootId2 = ObjectId.GenerateNewStringId(); var count = int.Parse(ConfigurationManager.AppSettings["batchAppendAsyncount"]); var batchSize = int.Parse(ConfigurationManager.AppSettings["batchSize"]); var printSize = count / 10; var finishedCount = 0; var eventStore = ObjectContainer.Resolve <IEventStore>(); var eventQueue = new Queue <DomainEventStream>(); for (var i = 1; i <= count; i++) { var aggregateRootId = i % 2 == 0 ? aggreagateRootId1 : aggreagateRootId2; var evnt = new TestEvent { AggregateRootId = aggregateRootId, AggregateRootStringId = aggregateRootId, Version = i }; var eventStream = new DomainEventStream(ObjectId.GenerateNewStringId(), aggregateRootId, "SampleAggregateRootTypeName", DateTime.Now, new IDomainEvent[] { evnt }); eventQueue.Enqueue(eventStream); } var watch = Stopwatch.StartNew(); var context = new BatchAppendContext { BatchSize = batchSize, PrintSize = printSize, FinishedCount = finishedCount, EventStore = eventStore, EventQueue = eventQueue, Watch = watch }; Console.WriteLine("start to batch append test, totalCount:" + count); DoBatchAppendAsync(context).Wait(); }
public Task <AsyncTaskResult <EventAppendResult> > AppendAsync(DomainEventStream eventStream) { if (_currentFailedCount < _expectFailedCount) { _currentFailedCount++; if (_failedType == FailedType.UnKnownException) { throw new Exception("AppendAsyncUnKnownException" + _currentFailedCount); } else if (_failedType == FailedType.IOException) { throw new IOException("AppendAsyncIOException" + _currentFailedCount); } else if (_failedType == FailedType.TaskIOException) { return(Task.FromResult(new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.Failed, "AppendAsyncError" + _currentFailedCount))); } } return(_inMemoryEventStore.AppendAsync(eventStream)); }
public Task <AsyncTaskResult <EventAppendResult> > AppendAsync(DomainEventStream eventStream) { var record = ConvertTo(eventStream); return(_ioHelper.TryIOFuncAsync(async() => { try { using (var connection = GetConnection()) { string sql = string.Format( "INSERT INTO {0} (AggregateRootId,AggregateRootTypeName,Version,CommandId,CreatedOn,Events) VALUES(@AggregateRootId,@AggregateRootTypeName,@Version,@CommandId,@CreatedOn,@Events)", GetTableName(record.AggregateRootId)); await connection.ExecuteAsync(sql, record); return new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.Success, EventAppendResult.Success); } } catch (MySqlException ex) { if (ex.Number == 1062 && ex.Message.Contains(_versionIndexName)) { return new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.Success, EventAppendResult.DuplicateEvent); } else if (ex.Number == 1062 && ex.Message.Contains(_commandIndexName)) { return new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.Success, EventAppendResult.DuplicateCommand); } _logger.Error(string.Format("Append event has sql exception, eventStream: {0}", eventStream), ex); return new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.IOException, ex.Message, EventAppendResult.Failed); } catch (Exception ex) { _logger.Error(string.Format("Append event has unknown exception, eventStream: {0}", eventStream), ex); return new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.Failed, ex.Message, EventAppendResult.Failed); } }, "AppendEventsAsync")); }
public Task <AsyncTaskResult <EventAppendResult> > AppendAsync(DomainEventStream eventStream) { var record = ConvertTo(eventStream); return(_ioHelper.TryIOFuncAsync <AsyncTaskResult <EventAppendResult> >(async() => { try { using (var connection = GetConnection()) { await connection.InsertToMySqlAsync(record, _tableName); return new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.Success, EventAppendResult.Success); } } catch (DbException ex) { //todo: this error number is from sql server, make sure it's same with mysql database. //if (ex.Number == 2627 && ex.Message.Contains(_primaryKeyName)) //{ // return new AsyncTaskResult<EventAppendResult>(AsyncTaskStatus.Success, EventAppendResult.DuplicateEvent); //} //else if (ex.Number == 2601 && ex.Message.Contains(_commandIndexName)) //{ // return new AsyncTaskResult<EventAppendResult>(AsyncTaskStatus.Success, EventAppendResult.DuplicateCommand); //} _logger.Error(string.Format("Append event has sql exception, eventStream: {0}", eventStream), ex); return new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.IOException, ex.Message, EventAppendResult.Failed); } catch (Exception ex) { _logger.Error(string.Format("Append event has unknown exception, eventStream: {0}", eventStream), ex); return new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.Failed, ex.Message, EventAppendResult.Failed); } }, "AppendEventsAsync")); }
public void ShouldThrowIfAggregateRootIdDoesNotMatch() { TestAggregateRoot aggregateRoot1 = new TestAggregateRoot(Guid.NewGuid()); TestAggregateRoot aggregateRoot2 = new TestAggregateRoot(Guid.NewGuid()); IAggregateRoot aggregateRoot1ExplicitCast = aggregateRoot1; IAggregateRoot aggregateRoot2ExplicitCast = aggregateRoot2; // Apply 3 domain events. aggregateRoot1.ChangeMe(Guid.NewGuid()); aggregateRoot1.ChangeMe(Guid.NewGuid()); aggregateRoot1.ChangeMe(Guid.NewGuid()); DomainEventStream stream1 = (DomainEventStream)aggregateRoot1ExplicitCast.GetDomainEventsMarkedForCommit(); // Apply 3 domain events. aggregateRoot2.ChangeMe(Guid.NewGuid()); aggregateRoot2.ChangeMe(Guid.NewGuid()); aggregateRoot2.ChangeMe(Guid.NewGuid()); DomainEventStream stream2 = (DomainEventStream)aggregateRoot2ExplicitCast.GetDomainEventsMarkedForCommit(); // Append 2 streams. stream1.Invoking(s1 => s1.AppendDomainEventStream(stream2)).Should().Throw <InvalidOperationException>(); }
public DomainEventStream GetAggregateRestoreEventStream(string postId, Post nullObject) { using (var connection = new SqlConnection(ConfigSettings.Forum_DBConnectionString)) { dynamic post = connection.QueryList <dynamic>(new { Id = postId }, Constants.PostTable).SingleOrDefault(); PostCreatedEvent postCreatedEvent = null; postCreatedEvent = new PostCreatedEvent( (string)post.Id, (int)post.Version, (string)post.Subject, (string)post.Body, (string)post.SectionId, (string)post.AuthorId); postCreatedEvent.Version = (int)post.Version; postCreatedEvent.Timestamp = (DateTime)post.CreatedOn; PostReplyStatisticInfo postReplyStatisticInfo = new PostReplyStatisticInfo( (string)post.MostRecentReplyId, (string)post.MostRecentReplierId, (DateTime)post.LastUpdateTime, (int)post.ReplyCount); PostReplyStatisticInfoChangedEvent postReplyStatisticInfoChangedEvent = new PostReplyStatisticInfoChangedEvent( (string)post.Id, (int)post.Version, postReplyStatisticInfo); postReplyStatisticInfoChangedEvent.Version = (int)post.Version; postReplyStatisticInfoChangedEvent.Timestamp = (DateTime)post.LastUpdateTime; IDomainEvent[] events = new IDomainEvent[] { postCreatedEvent, postReplyStatisticInfoChangedEvent }; DomainEventStream eventStream = new DomainEventStream(postId, postCreatedEvent.Version, events); return(eventStream); } }
public Task <AsyncTaskResult <EventAppendResult> > AppendAsync(DomainEventStream eventStream) { var record = ConvertTo(eventStream); return(_ioHelper.TryIOFuncAsync <AsyncTaskResult <EventAppendResult> >(async() => { try { using (var connection = GetConnection()) { await connection.InsertToMySqlAsync(record, _tableName); return new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.Success, EventAppendResult.Success); } } catch (MySql.Data.MySqlClient.MySqlException ex) { if (ex.Number == 1062 && ex.Message.Contains(_primaryKeyName)) { return new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.Success, EventAppendResult.DuplicateEvent); } else if (ex.Number == 1062 && ex.Message.Contains(_commandIndexName)) { return new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.Success, EventAppendResult.DuplicateCommand); } _logger.Error(string.Format("Append event has sql exception, eventStream: {0}", eventStream), ex); return new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.IOException, ex.Message, EventAppendResult.Failed); } catch (Exception ex) { _logger.Error(string.Format("Append event has unknown exception, eventStream: {0}", eventStream), ex); return new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.Failed, ex.Message, EventAppendResult.Failed); } }, "AppendEventsAsync")); }
public void update_concurrent_conflict_test() { var aggregateId = ObjectId.GenerateNewStringId(); var command = new CreateTestAggregateCommand { AggregateRootId = aggregateId, Title = "Sample Note" }; //执行创建聚合根的命令 var commandResult = _commandService.ExecuteAsync(command).Result; Assert.IsNotNull(commandResult); Assert.AreEqual(CommandStatus.Success, commandResult.Status); var note = _memoryCache.GetAsync <TestAggregate>(aggregateId).Result; Assert.IsNotNull(note); Assert.AreEqual("Sample Note", note.Title); Assert.AreEqual(1, ((IAggregateRoot)note).Version); //往EventStore直接插入事件,用于模拟并发冲突的情况 var eventStream = new DomainEventStream( ObjectId.GenerateNewStringId(), aggregateId, typeof(TestAggregate).FullName, DateTime.Now, new IDomainEvent[] { new TestAggregateTitleChanged("Changed Title") { AggregateRootId = aggregateId, Version = 2 } }, null); var result = _eventStore.BatchAppendAsync(new DomainEventStream[] { eventStream }).Result; Assert.IsNotNull(result); Assert.AreEqual(aggregateId, result.SuccessAggregateRootIdList[0]); _publishedVersionStore.UpdatePublishedVersionAsync("DefaultEventProcessor", typeof(TestAggregate).FullName, aggregateId, 2).Wait(); var commandList = new List <ICommand>(); for (var i = 0; i < 50; i++) { commandList.Add(new ChangeTestAggregateTitleCommand { AggregateRootId = aggregateId, Title = "Changed Note2" }); } var waitHandle = new ManualResetEvent(false); var count = 0L; foreach (var updateCommand in commandList) { _commandService.ExecuteAsync(updateCommand).ContinueWith(t => { Assert.IsNotNull(t.Result); var currentCommandResult = t.Result; Assert.IsNotNull(currentCommandResult); Assert.AreEqual(CommandStatus.Success, currentCommandResult.Status); var totalCount = Interlocked.Increment(ref count); if (totalCount == commandList.Count) { waitHandle.Set(); } }); } waitHandle.WaitOne(); note = _memoryCache.GetAsync <TestAggregate>(aggregateId).Result; Assert.IsNotNull(note); Assert.AreEqual(2 + commandList.Count, ((IAggregateRoot)note).Version); Assert.AreEqual("Changed Note2", note.Title); }
public Task <AsyncTaskResult <EventAppendResult> > AppendAsync(DomainEventStream eventStream) { return(Task.FromResult(new AsyncTaskResult <EventAppendResult>(AsyncTaskStatus.Success, null, Append(eventStream)))); }
private void HandleAggregateDuplicatedCommandAsync(ProcessingCommand processingCommand, IAggregateRoot dirtyAggregateRoot, DomainEventStream eventStream, int retryTimes) { var command = processingCommand.Message; _ioHelper.TryAsyncActionRecursively <AsyncTaskResult <HandledCommand> >("GetCommandAsync", () => _commandStore.GetAsync(command.Id), currentRetryTimes => HandleAggregateDuplicatedCommandAsync(processingCommand, dirtyAggregateRoot, eventStream, currentRetryTimes), result => { var existingHandledCommand = result.Data; if (existingHandledCommand != null) { HandleExistingHandledAggregateAsync(processingCommand, dirtyAggregateRoot, eventStream, existingHandledCommand, 0); } else { //到这里,说明当前command想添加到commandStore中时,提示command重复,但是尝试从commandStore中取出该command时却找不到该command。 //出现这种情况,我们就无法再做后续处理了,这种错误理论上不会出现,除非commandStore的Add接口和Get接口出现读写不一致的情况; //我们记录错误日志,然后认为当前command已被处理为失败。 var errorMessage = string.Format("Command exist in the command store, but we cannot get it from the command store. commandType:{0}, commandId:{1}", command.GetType().Name, command.Id); NotifyCommandExecuted(processingCommand, CommandStatus.Failed, null, errorMessage); } }, () => string.Format("[commandId:{0}]", command.Id), () => NotifyCommandExecuted(processingCommand, CommandStatus.Failed, null, "Get command async failed."), retryTimes); }
public void update_concurrent_conflict_test() { var aggregateId = ObjectId.GenerateNewStringId(); var command = new CreateTestAggregateCommand { AggregateRootId = aggregateId, Title = "Sample Note" }; //执行创建聚合根的命令 var asyncResult = _commandService.ExecuteAsync(command).Result; Assert.IsNotNull(asyncResult); Assert.AreEqual(AsyncTaskStatus.Success, asyncResult.Status); var commandResult = asyncResult.Data; Assert.IsNotNull(commandResult); Assert.AreEqual(CommandStatus.Success, commandResult.Status); var note = _memoryCache.Get <TestAggregate>(aggregateId); Assert.IsNotNull(note); Assert.AreEqual("Sample Note", note.Title); Assert.AreEqual(1, ((IAggregateRoot)note).Version); //往EventStore直接插入事件,用于模拟并发冲突的情况 var eventStream = new DomainEventStream( ObjectId.GenerateNewStringId(), aggregateId, typeof(TestAggregate).FullName, 2, DateTime.Now, new IDomainEvent[] { new TestAggregateTitleChanged("Changed Title") { AggregateRootId = aggregateId, Version = 2 } }, null); var result = _eventStore.AppendAsync(eventStream).Result; Assert.IsNotNull(result); Assert.AreEqual(AsyncTaskStatus.Success, result.Status); Assert.AreEqual(EventAppendResult.Success, result.Data); var result2 = _publishedVersionStore.UpdatePublishedVersionAsync("DefaultEventProcessor", typeof(TestAggregate).FullName, aggregateId, 2).Result; Assert.IsNotNull(result2); Assert.AreEqual(AsyncTaskStatus.Success, result2.Status); //执行修改聚合根的命令 var command2 = new ChangeTestAggregateTitleCommand { AggregateRootId = aggregateId, Title = "Changed Note2" }; asyncResult = _commandService.ExecuteAsync(command2).Result; Assert.IsNotNull(asyncResult); Assert.AreEqual(AsyncTaskStatus.Success, asyncResult.Status); commandResult = asyncResult.Data; Assert.IsNotNull(commandResult); Assert.AreEqual(CommandStatus.Success, commandResult.Status); note = _memoryCache.Get <TestAggregate>(aggregateId); Assert.IsNotNull(note); Assert.AreEqual(3, ((IAggregateRoot)note).Version); Assert.AreEqual("Changed Note2", note.Title); }
private void CommitAggregateChanges(ProcessingCommand processingCommand) { var command = processingCommand.Message; var context = processingCommand.CommandExecuteContext; var trackedAggregateRoots = context.GetTrackedAggregateRoots(); var dirtyAggregateRootCount = 0; var dirtyAggregateRoot = default(IAggregateRoot); var changedEvents = default(IEnumerable <IDomainEvent>); foreach (var aggregateRoot in trackedAggregateRoots) { var events = aggregateRoot.GetChanges(); if (events.Any()) { dirtyAggregateRootCount++; if (dirtyAggregateRootCount > 1) { var errorMessage = string.Format("Detected more than one aggregate created or modified by command. commandType:{0}, commandId:{1}", command.GetType().Name, command.Id); _logger.ErrorFormat(errorMessage); CompleteCommand(processingCommand, CommandStatus.Failed, typeof(string).FullName, errorMessage); return; } dirtyAggregateRoot = aggregateRoot; changedEvents = events; } } //如果当前command没有对任何聚合根做修改,框架仍然需要尝试获取该command之前是否有产生事件, //如果有,则需要将事件再次发布到MQ;如果没有,则完成命令,返回command的结果为NothingChanged。 //之所以要这样做是因为有可能当前command上次执行的结果可能是事件持久化完成,但是发布到MQ未完成,然后那时正好机器断电宕机了; //这种情况下,如果机器重启,当前command对应的聚合根从eventstore恢复的聚合根是被当前command处理过后的; //所以如果该command再次被处理,可能对应的聚合根就不会再产生事件了; //所以,我们要考虑到这种情况,尝试再次发布该命令产生的事件到MQ; //否则,如果我们直接将当前command设置为完成,即对MQ进行ack操作,那该command的事件就永远不会再发布到MQ了,这样就无法保证CQRS数据的最终一致性了。 if (dirtyAggregateRootCount == 0 || changedEvents == null || changedEvents.Count() == 0) { ProcessIfNoEventsOfCommand(processingCommand, 0); return; } //接受聚合根的最新修改 dirtyAggregateRoot.AcceptChanges(); //刷新聚合根的内存缓存 _memoryCache.UpdateAggregateRootCache(dirtyAggregateRoot); //构造出一个事件流对象 var commandResult = processingCommand.CommandExecuteContext.GetResult(); if (commandResult != null) { processingCommand.Items["CommandResult"] = commandResult; } var eventStream = new DomainEventStream( processingCommand.Message.Id, dirtyAggregateRoot.UniqueId, _typeNameProvider.GetTypeName(dirtyAggregateRoot.GetType()), changedEvents.First().Version, _timeProvider.GetCurrentTime(), changedEvents, processingCommand.Items); //异步将事件流提交到EventStore _eventService.CommitDomainEventAsync(new EventCommittingContext(dirtyAggregateRoot, eventStream, processingCommand)); }
public Task<AsyncTaskResult<EventAppendResult>> AppendAsync(DomainEventStream eventStream) { return Task.FromResult(new AsyncTaskResult<EventAppendResult>(AsyncTaskStatus.Success, null, Append(eventStream))); }
public async Task <AsyncResult <DomainEventAppendResult> > AppendAllAsync(DomainEventStream eventStream) { Assert.NotNull(eventStream, nameof(eventStream)); Assert.NotNullOrEmpty(eventStream.AggregateRootId, nameof(eventStream.AggregateRootId)); Assert.NotNullOrEmpty(eventStream.AggregateRootTypeName, nameof(eventStream.AggregateRootTypeName)); Assert.LengthGreaterThan(nameof(eventStream.Events), eventStream.Events.Count(), 0); var sql = string.Format(InsertSql, GetShardTableName(eventStream.AggregateRootId)); var storedEvents = eventStream.Events.Select(it => StoredDomainEventAdapter.ToStoredDomainEvent(it, _serializer)); try { using (var connection = new SqlConnection(_options.ConnectionString)) { await connection.OpenAsync(); var transaction = await connection.BeginTransactionAsync(); try { using (var copy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction)) { copy.BatchSize = eventStream.Events.Count(); copy.BulkCopyTimeout = _appendEventStreamTimeoutInSeconds; copy.DestinationTableName = GetShardTableName(eventStream.AggregateRootId); await copy.WriteToServerAsync(BuildStoreEventTableStructure()); await transaction.CommitAsync(); } return(new AsyncResult <DomainEventAppendResult>(AsyncStatus.Success)); } catch (Exception) { try { await transaction.RollbackAsync(); } catch (Exception ex) { _logger.LogError($"the method{nameof(AppendAllAsync)}'s transaction rollback failed.", ex); } throw; } } } catch (SqlException ex) { _logger.LogError("Apend all domain events associated with a aggregation has sql exception.", ex); if (ex.Number == 2601 && ex.Message.Contains(_versionIndexName)) { return(new AsyncResult <DomainEventAppendResult>(AsyncStatus.Success, DomainEventAppendResult.DuplicateEvent)); } else if (ex.Number == 2601 && ex.Message.Contains(_commandIndexName)) { return(new AsyncResult <DomainEventAppendResult>(AsyncStatus.Success, DomainEventAppendResult.DuplicateCommand)); } else { return(new AsyncResult <DomainEventAppendResult>(AsyncStatus.Failed)); } } catch (Exception ex) { _logger.LogError("Apend all domain events associated with a aggregation has unknown exception.", ex); return(new AsyncResult <DomainEventAppendResult>(AsyncStatus.Failed)); } }
public void ShouldThrowIfStreamToAppendIsNull() { DomainEventStream stream = new DomainEventStream(Guid.NewGuid()); stream.Invoking(s => s.AppendDomainEvent(null)).Should().Throw <ArgumentNullException>(); }
private void RefreshAggregateMemoryCache(DomainEventStream aggregateFirstEventStream) { try { var aggregateRootType = _typeNameProvider.GetType(aggregateFirstEventStream.AggregateRootTypeName); var aggregateRoot = _memoryCache.Get(aggregateFirstEventStream.AggregateRootId, aggregateRootType); if (aggregateRoot == null) { aggregateRoot = _aggregateRootFactory.CreateAggregateRoot(aggregateRootType); aggregateRoot.ReplayEvents(new DomainEventStream[] { aggregateFirstEventStream }); _memoryCache.Set(aggregateRoot); if (_logger.IsDebugEnabled) { _logger.DebugFormat("Aggregate added into memory, commandId:{0}, aggregateRootType:{1}, aggregateRootId:{2}, aggregateRootVersion:{3}", aggregateFirstEventStream.CommandId, aggregateRootType.Name, aggregateRoot.UniqueId, aggregateRoot.Version); } } } catch (Exception ex) { _logger.Error(string.Format("Refresh memory cache by aggregate first event stream failed, {0}", aggregateFirstEventStream), ex); } }
private StreamRecord ConvertTo(DomainEventStream eventStream) { return new StreamRecord { CommandId = eventStream.CommandId, AggregateRootId = eventStream.AggregateRootId, AggregateRootTypeName = eventStream.AggregateRootTypeName, Version = eventStream.Version, CreatedOn = eventStream.Timestamp, Events = _jsonSerializer.Serialize(_eventSerializer.Serialize(eventStream.Events)) }; }
public async Task Should_Query_Aggregate_Events_Async() { //Arrange var eventStream1 = GetTestDomainEventStream(ObjectId.GenerateNewStringId()); var eventStream2 = new DomainEventStream(ObjectId.GenerateNewStringId(), eventStream1.AggregateRootId, "typename", DateTime.Now, new List <DomainEvent <string> >() { new Test1DomainEvent() { AggregateRootId = eventStream1.AggregateRootId, Name = "test1", Version = 2, }, new Test2DomainEvent() { AggregateRootId = eventStream1.AggregateRootId, Name = "test2", Version = 2, }, new Test3DomainEvent() { AggregateRootId = eventStream1.AggregateRootId, Name = "test3", Version = 2, }, }); var eventStream3 = new DomainEventStream(ObjectId.GenerateNewStringId(), eventStream1.AggregateRootId, "typename", DateTime.Now, new List <DomainEvent <string> >() { new Test1DomainEvent() { AggregateRootId = eventStream1.AggregateRootId, Name = "test1", Version = 3, }, new Test2DomainEvent() { AggregateRootId = eventStream1.AggregateRootId, Name = "test2", Version = 3, }, new Test3DomainEvent() { AggregateRootId = eventStream1.AggregateRootId, Name = "test3", Version = 3, }, }); await _store.BatchAppendAsync(new List <DomainEventStream> { eventStream1 }); await _store.BatchAppendAsync(new List <DomainEventStream> { eventStream2 }); await _store.BatchAppendAsync(new List <DomainEventStream> { eventStream3 }); //Act var events = await _store.QueryAggregateEventsAsync(eventStream1.AggregateRootId, eventStream1.AggregateRootTypeName, 1, 3); events.Count().ShouldBe(3); }
private void UpdateAggregateMemoryCacheToLatestVersion(DomainEventStream eventStream) { try { _memoryCache.RefreshAggregateFromEventStore(eventStream.AggregateRootTypeName, eventStream.AggregateRootId); } catch (Exception ex) { _logger.Error(string.Format("Try to refresh aggregate in-memory from event store failed, eventStream: {0}", eventStream), ex); } }
private void CommitAggregateChangesAsync(ProcessingCommand processingCommand, IAggregateRoot dirtyAggregateRoot, DomainEventStream eventStream, HandledCommand handledCommand, int retryTimes) { _ioHelper.TryAsyncActionRecursively <AsyncTaskResult <CommandAddResult> >("AddCommandAsync", () => _commandStore.AddAsync(handledCommand), currentRetryTimes => CommitAggregateChangesAsync(processingCommand, dirtyAggregateRoot, eventStream, handledCommand, currentRetryTimes), result => { var commandAddResult = result.Data; if (commandAddResult == CommandAddResult.Success) { _eventService.CommitDomainEventAsync(new EventCommittingContext(dirtyAggregateRoot, eventStream, processingCommand)); } else if (commandAddResult == CommandAddResult.DuplicateCommand) { HandleAggregateDuplicatedCommandAsync(processingCommand, dirtyAggregateRoot, eventStream, 0); } else { NotifyCommandExecuted(processingCommand, CommandStatus.Failed, null, "Add command async failed."); } }, () => string.Format("[handledCommand:{0}]", handledCommand), () => NotifyCommandExecuted(processingCommand, CommandStatus.Failed, null, "Add command async failed."), retryTimes); }
public void create_concurrent_conflict_and_then_update_many_times_test2() { var aggregateId = ObjectId.GenerateNewStringId(); var commandId = ObjectId.GenerateNewStringId(); //往EventStore直接插入事件,用于模拟并发冲突的情况 var eventStream = new DomainEventStream( commandId, aggregateId, typeof(TestAggregate).FullName, 1, DateTime.Now, new IDomainEvent[] { new TestAggregateTitleChanged("Note Title") { AggregateRootId = aggregateId, Version = 1 } }, null); var result = _eventStore.AppendAsync(eventStream).Result; Assert.IsNotNull(result); Assert.AreEqual(AsyncTaskStatus.Success, result.Status); Assert.AreEqual(EventAppendResult.Success, result.Data); var result2 = _publishedVersionStore.UpdatePublishedVersionAsync("DefaultEventProcessor", typeof(TestAggregate).FullName, aggregateId, 1).Result; Assert.IsNotNull(result2); Assert.AreEqual(AsyncTaskStatus.Success, result2.Status); var commandList = new List <ICommand>(); commandList.Add(new CreateTestAggregateCommand { Id = commandId, AggregateRootId = aggregateId, Title = "Sample Note" }); for (var i = 0; i < 50; i++) { commandList.Add(new ChangeTestAggregateTitleCommand { AggregateRootId = aggregateId, Title = "Changed Note Title" }); } var waitHandle = new ManualResetEvent(false); var count = 0L; var createCommandSuccess = false; foreach (var updateCommand in commandList) { _commandService.ExecuteAsync(updateCommand).ContinueWith(t => { Assert.IsNotNull(t.Result); Assert.AreEqual(AsyncTaskStatus.Success, t.Result.Status); var commandResult = t.Result.Data; Assert.IsNotNull(commandResult); Assert.AreEqual(CommandStatus.Success, commandResult.Status); if (commandResult.CommandId != commandId) { var totalCount = Interlocked.Increment(ref count); if (totalCount == commandList.Count - 1) { waitHandle.Set(); } } else { createCommandSuccess = true; } }); } waitHandle.WaitOne(); var note = _memoryCache.GetAsync <TestAggregate>(aggregateId).Result; Assert.IsNotNull(note); Assert.AreEqual(true, createCommandSuccess); Assert.AreEqual(commandList.Count, ((IAggregateRoot)note).Version); }
private void HandleExistingHandledAggregateAsync(ProcessingCommand processingCommand, IAggregateRoot dirtyAggregateRoot, DomainEventStream eventStream, HandledCommand existingHandledCommand, int retryTimes) { var command = processingCommand.Message; _ioHelper.TryAsyncActionRecursively <AsyncTaskResult <DomainEventStream> >("FindEventStreamByCommandIdAsync", () => _eventStore.FindAsync(existingHandledCommand.AggregateRootId, command.Id), currentRetryTimes => HandleExistingHandledAggregateAsync(processingCommand, dirtyAggregateRoot, eventStream, existingHandledCommand, currentRetryTimes), result => { var existingEventStream = result.Data; if (existingEventStream != null) { //如果当前command已经被持久化过了,且该command产生的事件也已经被持久化了,则只要再做一遍发布事件的操作 _eventService.PublishDomainEventAsync(processingCommand, existingEventStream); } else { //如果当前command已经被持久化过了,但事件没有被持久化,则需要重新提交当前command所产生的事件; _eventService.CommitDomainEventAsync(new EventCommittingContext(dirtyAggregateRoot, eventStream, processingCommand)); } }, () => string.Format("[aggregateRootId:{0}, commandId:{1}]", existingHandledCommand.AggregateRootId, command.Id), () => NotifyCommandExecuted(processingCommand, CommandStatus.Failed, null, "Find event stream by command id async failed."), retryTimes); }
public void create_concurrent_conflict_not_enable_batch_insert_test() { _eventStore.SupportBatchAppendEvent = false; try { var aggregateId = ObjectId.GenerateNewStringId(); var commandId = ObjectId.GenerateNewStringId(); //往EventStore直接插入事件,用于模拟并发冲突的情况 var eventStream = new DomainEventStream( commandId, aggregateId, typeof(TestAggregate).FullName, 1, DateTime.Now, new IDomainEvent[] { new TestAggregateTitleChanged("Note Title") { AggregateRootId = aggregateId, Version = 1 } }, null); var result = _eventStore.AppendAsync(eventStream).Result; Assert.IsNotNull(result); Assert.AreEqual(AsyncTaskStatus.Success, result.Status); Assert.AreEqual(EventAppendResult.Success, result.Data); var result2 = _publishedVersionStore.UpdatePublishedVersionAsync("DefaultEventProcessor", typeof(TestAggregate).FullName, aggregateId, 1).Result; Assert.IsNotNull(result2); Assert.AreEqual(AsyncTaskStatus.Success, result2.Status); //执行创建聚合根的命令 var command = new CreateTestAggregateCommand { AggregateRootId = aggregateId, Title = "Sample Note" }; var asyncResult = _commandService.ExecuteAsync(command).Result; Assert.IsNotNull(asyncResult); Assert.AreEqual(AsyncTaskStatus.Success, asyncResult.Status); var commandResult = asyncResult.Data; Assert.IsNotNull(commandResult); Assert.AreEqual(CommandStatus.Failed, commandResult.Status); Assert.AreEqual("Duplicate aggregate creation.", commandResult.Result); var note = _memoryCache.GetAsync <TestAggregate>(aggregateId).Result; Assert.IsNotNull(note); Assert.AreEqual("Note Title", note.Title); Assert.AreEqual(1, ((IAggregateRoot)note).Version); //执行创建聚合根的命令 command = new CreateTestAggregateCommand { Id = commandId, AggregateRootId = aggregateId, Title = "Sample Note" }; asyncResult = _commandService.ExecuteAsync(command).Result; Assert.IsNotNull(asyncResult); Assert.AreEqual(AsyncTaskStatus.Success, asyncResult.Status); commandResult = asyncResult.Data; Assert.IsNotNull(commandResult); Assert.AreEqual(CommandStatus.Success, commandResult.Status); note = _memoryCache.GetAsync <TestAggregate>(aggregateId).Result; Assert.IsNotNull(note); Assert.AreEqual("Note Title", note.Title); Assert.AreEqual(1, ((IAggregateRoot)note).Version); } finally { _eventStore.SupportBatchAppendEvent = true; } }