private async Task <List <IEventMessageDraft> > CreateEventMessagesAsync(IEventSourcedAggregateRoot aggregate, IReadOnlyCollection <DomainAggregateEvent> events) { var messages = new List <IEventMessageDraft>(); Guid?aggregateClassId = entityTypeManager.TryGetClassInfoByClrType(aggregate.GetType())?.Id; if (aggregateClassId == null) { throw new InvalidOperationException($"Cannot save event sourced aggregate of type {aggregate.GetType()}: its class ID has not been defined"); } foreach (DomainAggregateEvent ev in events) { IEventMessageDraft message = await eventMessageFactory.CreateMessageAsync(ev); message.SetMetadata(BasicEventMetadataNames.AggregateClassId, aggregateClassId.Value.ToString()); message.SetMetadata(BasicEventMetadataNames.AggregateVersion, (aggregate.Version + 1).ToString()); if (aggregate is ITenantOwned tenantOwned) { message.SetMetadata(BasicEventMetadataNames.AggregateTenantId, tenantOwned.TenantId?.ToString()); } messages.Add(message); } return(messages); }
private IEventSourcedAggregateRoot ConstructAndLoadEntityFromEvents(Guid aggregateId, IReadOnlyDictionary <string, string> eventStreamMetadata, IReadOnlyCollection <IEventStoreRecord> eventRecords) { int version = (int)(eventRecords.LastOrDefault()?.StreamSequenceNumber ?? 0); var events = eventRecords.Select(x => x.Event as DomainAggregateEvent ?? throw new InvalidOperationException( $"Cannot load event sourced aggregate ID {aggregateId}: event stream contains non-DomainAggregateEvent events of type {x.Event.GetType().FullName}")) .ToList(); AggregateState state = new AggregateState(version, events); if (!eventStreamMetadata.TryGetValue(AggregateEventStreamMetadataNames.ClassId, out string classIdString)) { throw new InvalidOperationException($"Cannot load event sourced aggregate ID {aggregateId}: aggregate class ID not found in event stream metadata"); } Guid classId = Guid.Parse(classIdString); Type entityType = entityTypeManager.GetClassInfoByClassId(classId).ClrType; IEventSourcedAggregateRoot aggregate = (IEventSourcedAggregateRoot)ConstructEntity(entityType, aggregateId); aggregate.LoadState(state); return(aggregate); }
private void CheckEvents(IEventSourcedAggregateRoot aggregate, IEnumerable <DomainAggregateEvent> uncommitedEvents) { foreach (DomainAggregateEvent _event in uncommitedEvents) { if (_event.AggregateId != aggregate.Id) { throw new ArgumentException($"Domain aggregate event '{_event.GetType().FullName}' queued for saving has an invalid or empty AggregateId value: {_event.AggregateId}"); } } }
private async Task SaveEventsAsync(IEventSourcedAggregateRoot aggregateRoot) { using (var eventStream = this.testee.OpenStream <MyDynamicEventSourcedAggregateRoot>(this.aggregateId)) { await eventStream.SaveAsync( aggregateRoot.UncommittedEvents.OfType <VersionableEvent>(), aggregateRoot.Version, new Dictionary <string, object>()).ConfigureAwait(false); } aggregateRoot.CommitEvents(); }
private async Task <T[]> DoFindManyAsync <T>(Guid[] ids, bool throwOnError) where T : class, IAggregateRoot { var result = new List <T>(); List <Guid> missingIds = null; foreach (Guid id in ids) { IEventSourcedAggregateRoot aggregate = FindLoadedAggregate(id); if (aggregate != null) { var typedAggregate = CheckAggregate <T>(id, aggregate, throwOnError); result.Add(typedAggregate); } else { if (missingIds == null) { missingIds = new List <Guid>(); } missingIds.Add(id); } } if (missingIds?.Count > 0) { var loaded = await LoadAggregatesAsync(missingIds.ToArray()); if (throwOnError) { var notFoundIds = missingIds.Where(x => !loaded.ContainsKey(x)).ToArray(); if (notFoundIds.Length > 0) { throw new EntityNotFoundException($"Aggregate(s) of type {typeof(T)} with ID(s) {string.Join(", ", notFoundIds)} were not found"); } } foreach (var aggregatePair in loaded) { var typedAggregate = CheckAggregate <T>(aggregatePair.Key, aggregatePair.Value, throwOnError); if (typedAggregate != null) { result.Add(typedAggregate); aggregates.Add(aggregatePair.Key, aggregatePair.Value); } } } return(result.ToArray()); }
private async Task CheckpointAsync( ITimeout timeout, IEventSourcedAggregateRoot aggregateRoot, CancellationToken token) { if (timeout.Canceled) { return; } await _checkpointManager.CheckpointAsync(aggregateRoot, token); timeout.Timer.NewTimeout(new FunctionTimerTask(async it => await CheckpointAsync(it, aggregateRoot, token)), TimeSpan.FromSeconds(_options.StepInSeconds)); }
private async Task <T> DoFindAsync <T>(Guid id, bool throwOnError) where T : class, IAggregateRoot { IEventSourcedAggregateRoot aggregate = FindLoadedAggregate(id); if (aggregate == null) { aggregate = await LoadAggregateAsync(id); if (aggregate != null) { aggregates.Add(aggregate.Id, aggregate); } } return(CheckAggregate <T>(id, aggregate, throwOnError)); }
public void Set(IEventSourcedAggregateRoot aggregateRoot) { if (aggregateRoot == null) { return; } var aggregateRootId = aggregateRoot.Id; var aggregateRootType = aggregateRoot.GetType(); var cacheKey = GetCacheKey(aggregateRootId, aggregateRootType); if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug($"Setting aggregate root to memory cache[LRU], Id: {aggregateRootId}, Type: {aggregateRootType}."); } _cache.Add(cacheKey, aggregateRoot); }
public IEventSourcedAggregateRoot ConstructAndLoadEntityFromEvents(Guid aggregateId, IReadOnlyDictionary <string, string> eventStreamMetadata, IReadOnlyCollection <IEventStoreRecord> eventRecords) { if (!eventStreamMetadata.TryGetValue(AggregateEventStreamMetadataNames.ClassId, out string classIdString)) { throw new InvalidOperationException($"Cannot load event sourced aggregate ID {aggregateId}: aggregate class ID not found in event stream metadata"); } Guid classId = Guid.Parse(classIdString); Type entityType = entityTypeManager.GetClassInfoByClassId(classId).ClrType; IEnumerable <IEventMessage <DomainAggregateEvent> > eventMessages; try { eventMessages = eventRecords.Select(EventStoreEventMessage.FromRecord) .Cast <IEventMessage <DomainAggregateEvent> >(); } catch (InvalidCastException e) { throw new InvalidOperationException( $"Cannot load event sourced aggregate ID {aggregateId}: event stream contains non-DomainAggregateEvent events", e); } var upgradedEvents = eventStreamUpgrader.UpgradeStream(eventMessages, eventStreamMetadata); var events = upgradedEvents.Select(x => x.Event).ToArray(); int version = (int)(eventRecords.LastOrDefault()?.AdditionalMetadata.GetAggregateVersion() // use non-upgraded event records to preserve the versions ?? eventRecords.LastOrDefault()?.StreamSequenceNumber ?? 0); AggregateState state = new AggregateState(version, events); IEventSourcedAggregateRoot aggregate = (IEventSourcedAggregateRoot)ConstructEntity(entityType, aggregateId); aggregate.LoadState(state); return(aggregate); }
public async Task CheckpointAsync( IEventSourcedAggregateRoot aggregateRoot, CancellationToken token = default) { var aggregateRootId = aggregateRoot.Id; var aggregateRootType = aggregateRoot.GetType().FullName; var aggregateGeneration = aggregateRoot.Generation; var aggregateVersion = aggregateRoot.Version; var metrics = await _eventStateBackend.StatMetricsAsync(aggregateRootId, aggregateGeneration, token); if (metrics.TriggerCheckpoint(_options)) { var nextGeneration = ++aggregateRoot.Generation; var checkpoint = new AggregateRootCheckpoint <IEventSourcedAggregateRoot>(aggregateRoot.Id, aggregateRoot.GetType(), nextGeneration, aggregateRoot.Version, aggregateRoot); var message = $"id: {aggregateRootId}, Type: {aggregateRootType}, Generation: {nextGeneration}, Version: {aggregateVersion}, UnCheckpointedBytes: {metrics.UnCheckpointedBytes} >= {_options.UnCheckpointedBytes}, UnCheckpointedCount: {metrics.UnCheckpointedCount} >= {_options.UnCheckpointedCount}"; try { await _checkpointStateBackend.AppendAsync(checkpoint, token); } catch (Exception e) { _logger.LogInformation($"Checkpointing the aggregate root, {message} has a unknown exception: {LogFormatter.PrintException(e)}."); return; } _logger.LogInformation($"Checkpointed the aggregate root, {message}."); } else { _logger.LogInformation($"No triggering checkpoint for the aggregate root, id: {aggregateRootId}, Type: {aggregateRootType}, Generation: {aggregateGeneration}, Version: {aggregateVersion}, UnCheckpointedBytes: {metrics.UnCheckpointedBytes} < {_options.UnCheckpointedBytes}, UnCheckpointedCount: {metrics.UnCheckpointedCount} < {_options.UnCheckpointedCount}."); } }
public bool RaiseSingleEventOf <T>(IEventSourcedAggregateRoot eventSourcedAggregateRoot) { return(eventSourcedAggregateRoot.UncommittedChanges().OfType <T>().SingleOrDefault() != null); }
private void CacheAndCheckpoint(IEventSourcedAggregateRoot aggregateRoot, CancellationToken token) { _memoryCache.Set(aggregateRoot); _timer.NewTimeout(new FunctionTimerTask(async it => await CheckpointAsync(it, aggregateRoot, token)), TimeSpan.FromSeconds(_options.StepInSeconds)); }