コード例 #1
0
        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);
        }
コード例 #2
0
        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);
        }
コード例 #3
0
 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}");
         }
     }
 }
コード例 #4
0
        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();
        }
コード例 #5
0
        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());
        }
コード例 #6
0
        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));
        }
コード例 #7
0
        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));
        }
コード例 #8
0
        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);
        }
コード例 #9
0
        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);
        }
コード例 #10
0
        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}.");
            }
        }
コード例 #11
0
 public bool RaiseSingleEventOf <T>(IEventSourcedAggregateRoot eventSourcedAggregateRoot)
 {
     return(eventSourcedAggregateRoot.UncommittedChanges().OfType <T>().SingleOrDefault() != null);
 }
コード例 #12
0
        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));
        }