/// <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); }
/// <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); }
/// <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); }
/// <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); } }
/// <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);
public AccountRepository(IDomainEventStream<Account, AccountId> stream) { this.stream = stream; }
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)); }
/// <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)); }