/// <summary>
        /// Ensure that the passed-in domain event stream is not null.
        /// </summary>
        /// <param name="history">Domain event stream.</param>
        /// <returns>Valid domain event stream.</returns>
        private static IDomainEventStream <TId> EnsureValidDomainEventStream(IDomainEventStream <TId> history)
        {
            if (history == null)
            {
                throw new ArgumentNullException(nameof(history));
            }

            return(history);
        }
 /// <summary>
 /// Constructor to build this aggregate from the domain event stream.
 /// </summary>
 /// <param name="history">Domain event stream.</param>
 public EventSourcedAggregate(IDomainEventStream <TId> history)
     : this(EnsureValidDomainEventStream(history).AggregateId)
 {
     // History events are events that are already saved to event store.
     // So, just invoke the applier without tracking events.
     foreach (IDomainEvent domainEvent in history)
     {
         InvokeDomainEventApplier(domainEvent, false);
     }
 }
        public async Task SaveAsync(TAggregate aggregateRoot, CancellationToken cancellationToken = default(CancellationToken))
        {
            // Get a copy of the uncommited domain events before saving.
            IDomainEventStream <TAggregateId> domainEventStreamToSave = aggregateRoot.GetUncommitedDomainEvents();

            await _domainEventStore.SaveAsync(aggregateRoot, cancellationToken).ConfigureAwait(false);

            // No need to await. Any publish errors will be communicated through OnError event.
            // Not passing cancellation token since event notification should not be cancelled.
            Task publishTask = PublishDomainEventsAsync(domainEventStreamToSave);
        }
        /// <summary>
        /// Persist aggregate to the event store and publish the events.
        /// </summary>
        /// <param name="aggregateRoot">Aggregate to persist.</param>
        public void Save(TAggregate aggregateRoot)
        {
            // Get a copy of the uncommited domain events before saving.
            IDomainEventStream <TAggregateId> domainEventStreamToSave = aggregateRoot.GetUncommitedDomainEvents();

            _domainEventStore.Save(aggregateRoot);

            // No need to await. Any publish errors will be communicated through OnError event.
            // Not passing cancellation token since event notification should not be cancelled.
            Task publishTask = PublishDomainEventsAsync(domainEventStreamToSave);
        }
Ejemplo n.º 5
0
 /// <summary>
 /// Get domain events of aggregate from the beginning up to the specified version asynchronously.
 /// </summary>
 /// <param name="aggreggateId">ID of the aggregate.</param>
 /// <param name="fromVersion">Aggregate version to start retrieving domain events from.</param>
 /// <param name="toVersion">Target aggregate version.</param>
 /// <param name="cancellationToken">Cancellation token.</param>
 /// <returns>Domain events for the aggregate with the specified version.</returns>
 public virtual Task <IDomainEventStream <TAggregateId> > GetDomainEventStreamAsync(TAggregateId aggreggateId, int fromVersion, int toVersion, CancellationToken cancellationToken = default(CancellationToken))
 {
     try
     {
         IDomainEventStream <TAggregateId> stream = GetDomainEventStream(aggreggateId, fromVersion, toVersion);
         return(Task.FromResult(stream));
     }
     catch (Exception ex)
     {
         return(TaskUtility.FromException <IDomainEventStream <TAggregateId> >(ex));
     }
 }
            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);
            }
Ejemplo n.º 7
0
        /// <summary>
        /// Creates a new domain event stream which has the appended domain event stream.
        /// </summary>
        /// <param name="streamToAppend">Domain event stream to append to this domain event stream.</param>
        /// <returns>New instance of domain event stream with the appended domain event stream.</returns>
        public DomainEventStream AppendDomainEventStream(IDomainEventStream streamToAppend)
        {
            if (streamToAppend == null)
            {
                throw new ArgumentNullException(nameof(streamToAppend));
            }

            if (AggregateRootId != streamToAppend.AggregateRootId)
            {
                throw new InvalidOperationException("Cannot append domain events belonging to a different aggregate root.");
            }

            return(new DomainEventStream(AggregateRootId, this.Concat(streamToAppend)));
        }
        /// <summary>
        /// Save aggregate root and publish uncommitted domain events.
        /// </summary>
        /// <param name="aggregateRoot">Aggregate root.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>Asynchronous task.</returns>
        public async Task SaveAsync(TAggregateRoot aggregateRoot, CancellationToken cancellationToken = default(CancellationToken))
        {
            // Get a copy of domain events marked for commit.
            IDomainEventStream domainEventsCopy = aggregateRoot.GetDomainEventsMarkedForCommit();

            // Save aggregate root.
            await _decoratedRepository.SaveAsync(aggregateRoot);

            // Publish after saving.
            await _domainEventPublisher.PublishAsync(domainEventsCopy);

            // Clear domain events after publishing.
            aggregateRoot.MarkDomainEventsAsCommitted();
        }
            public async Task Should_Append_To_Domain_Event_Store()
            {
                IDomainEventAsyncStore <TestAggregate, Guid> eventStore = Factory.CreateEventAsyncStore <TestAggregate, Guid>();

                // Create aggregate.
                TestAggregate aggregate = new TestAggregate(Guid.NewGuid());
                await eventStore.SaveAsync(aggregate);

                IDomainEventStream <Guid> stream = await eventStore.GetDomainEventStreamAsync(aggregate.Id);

                Assert.NotNull(stream);
                Assert.Equal(aggregate.Id, stream.AggregateId);
                Assert.Equal(1, stream.DomainEventCount);
            }
Ejemplo n.º 10
0
        /// <summary>
        /// Persist aggregate to the event store.
        /// </summary>
        /// <param name="aggregateRoot">Aggregate to persist.</param>
        public void Save(TAggregate aggregateRoot)
        {
            try
            {
                IDomainEventStream <TAggregateId> domainEventsToCommit = aggregateRoot.GetUncommitedDomainEvents();

                Commit(domainEventsToCommit);

                // Clear after committing and publishing.
                aggregateRoot.ClearUncommitedDomainEvents();
            }
            catch (Exception ex)
            {
                OnCommitError(ex);
            }
        }
        /// <summary>
        /// Persist aggregate to the event store asynchronously.
        /// </summary>
        /// <param name="aggregateRoot">Aggregate to persist.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>Task which can be awaited asynchronously.</returns>
        public async Task SaveAsync(TAggregate aggregateRoot, CancellationToken cancellationToken = default(CancellationToken))
        {
            try
            {
                // Get uncommited events.
                IDomainEventStream <TAggregateId> domainEventsToCommit = aggregateRoot.GetUncommitedDomainEvents();

                await CommitAsync(domainEventsToCommit, cancellationToken).ConfigureAwait(false);

                // Clear after committing and publishing.
                aggregateRoot.ClearUncommitedDomainEvents();
            }
            catch (Exception ex)
            {
                OnCommitError(ex);
            }
        }
Ejemplo n.º 12
0
        /// <summary>
        /// Commit the domain event to the in-memory store.
        /// </summary>
        /// <param name="domainEventStreamToCommit">Domain event to store.</param>
        protected virtual void Commit(IDomainEventStream <TAggregateId> domainEventStreamToCommit)
        {
            DomainEventStream <TAggregateId> existingStream;

            if (_domainEventStreamsByAggregateId.TryGetValue(domainEventStreamToCommit.AggregateId, out existingStream))
            {
                // Aggregate stream already exists.
                // Append and update.
                _domainEventStreamsByAggregateId[domainEventStreamToCommit.AggregateId] = existingStream.AppendDomainEventStream(domainEventStreamToCommit);
            }
            else
            {
                // Save.
                _domainEventStreamsByAggregateId.Add(domainEventStreamToCommit.AggregateId,
                                                     new DomainEventStream <TAggregateId>(domainEventStreamToCommit.AggregateId, domainEventStreamToCommit));
            }
        }
            public void Should_Append_To_Domain_Event_Store()
            {
                IDomainEventStore <TestAggregate, Guid> eventStore = Factory.CreateEventStore <TestAggregate, Guid>();

                // Create aggregate.
                TestAggregate aggregate = new TestAggregate(Guid.NewGuid());

                eventStore.Save(aggregate);

                IDomainEventStream <Guid> stream = eventStore.GetDomainEventStream(aggregate.Id);

                var fromStream = new TestAggregate(stream);

                Assert.NotNull(stream);
                Assert.Equal(aggregate.Id, fromStream.Id);

                // 1 domain event in total: Created event.
                Assert.Equal(1, stream.DomainEventCount);
            }
        /// <summary>
        /// Creates a new domain event stream which has the appended domain event stream.
        /// </summary>
        /// <param name="streamToAppend">Domain event stream to append to this domain event stream.</param>
        /// <returns>New instance of domain event stream with the appended domain event stream.</returns>
        public DomainEventStream <TAggregateId> AppendDomainEventStream(IDomainEventStream <TAggregateId> streamToAppend)
        {
            if (streamToAppend == null)
            {
                throw new ArgumentNullException(nameof(streamToAppend));
            }

            if (!AggregateId.Equals(streamToAppend.AggregateId))
            {
                throw new InvalidOperationException("Cannot append domain event belonging to a different aggregate.");
            }

            if (EndVersion >= streamToAppend.BeginVersion)
            {
                throw new DomainEventVersionConflictException("Domain event streams contain some entries with overlapping versions.");
            }

            return(new DomainEventStream <TAggregateId>(AggregateId, this.Concat(streamToAppend)));
        }
            public async Task GetDomainEventStreamAsync_Should_Retrieve_Aggregate_Stream()
            {
                IDomainEventAsyncStore <TestAggregate, Guid> eventStore = Factory.CreateEventAsyncStore <TestAggregate, Guid>();

                // Create and modify aggregate.
                TestAggregate aggregate = new TestAggregate(Guid.NewGuid());

                aggregate.ExecuteSomeOperation("I was modified!~");
                await eventStore.SaveAsync(aggregate);

                IDomainEventStream <Guid> stream = await eventStore.GetDomainEventStreamAsync(aggregate.Id);

                Assert.NotNull(stream);
                Assert.Equal(aggregate.Id, stream.AggregateId);

                // 2 domain events in total: Created + Modified events.
                Assert.Equal(2, stream.DomainEventCount);

                // Stream starts from version 1 to 2.
                Assert.Equal(1, stream.BeginVersion);
                Assert.Equal(2, stream.EndVersion);
            }
 /// <summary>
 /// Commit the domain event to the store asynchronously.
 /// </summary>
 /// <param name="domainEventStreamToCommit">Domain event to store.</param>
 /// <param name="cancellationToken">Cancellation token.</param>
 /// <returns>Task which can be awaited asynchronously.</returns>
 protected abstract Task CommitAsync(IDomainEventStream <TAggregateId> domainEventStreamToCommit, CancellationToken cancellationToken = default(CancellationToken));
 /// <summary>
 /// Commit the domain event to the store.
 /// </summary>
 /// <param name="domainEventStreamToCommit">Domain event to store.</param>
 protected abstract void Commit(IDomainEventStream <TAggregateId> domainEventStreamToCommit);
Ejemplo n.º 18
0
 public AccountRepository(IDomainEventStream<Account, AccountId> stream)
 {
     this.stream = stream;
 }
Ejemplo n.º 19
0
 public TestAggregate(IDomainEventStream <Guid> history)
     : base(history)
 {
 }
 /// <summary>
 /// Publishes the domain event to event subscribers.
 /// Default implementation publishes domain events in background.
 /// </summary>
 /// <param name="eventStream">Domain events to publish.</param>
 /// <returns>Asynchronous task.</returns>
 protected virtual Task PublishDomainEventsAsync(IDomainEventStream <TAggregateId> eventStream)
 {
     return(_publisher.PublishAsync(eventStream));
 }
Ejemplo n.º 21
0
 /// <summary>
 /// Publish domain events.
 /// </summary>
 /// <param name="domainEvents">Domain events to publish.</param>
 /// <param name="cancellationToken">Cancellation token.</param>
 /// <returns>Asynchronous task.</returns>
 public Task PublishAsync(IDomainEventStream domainEvents, CancellationToken cancellationToken = default(CancellationToken))
 {
     return(_eventDelegator.SendAllAsync(domainEvents, cancellationToken));
 }