diff --git a/samples/EasyWay.Samples/Domain/CreatedSampleAggragete.cs b/samples/EasyWay.Samples/Domain/CreatedSampleAggragete.cs index 956adb9..5a96e13 100644 --- a/samples/EasyWay.Samples/Domain/CreatedSampleAggragete.cs +++ b/samples/EasyWay.Samples/Domain/CreatedSampleAggragete.cs @@ -1,7 +1,7 @@ namespace EasyWay.Samples.Domain { - public class CreatedSampleAggragete : DomainEvent + public sealed class CreatedSampleAggragete : DomainEvent { - public Guid Id { get; } = Guid.NewGuid(); + public string test { get; } = "TEST"; } } diff --git a/samples/EasyWay.Samples/Events/SampleEventHandler1.cs b/samples/EasyWay.Samples/Events/SampleEventHandler1.cs index f32b46e..0e9ff13 100644 --- a/samples/EasyWay.Samples/Events/SampleEventHandler1.cs +++ b/samples/EasyWay.Samples/Events/SampleEventHandler1.cs @@ -1,10 +1,11 @@ -using EasyWay.Samples.Domain; +using EasyWay.Events.DomainEvents; +using EasyWay.Samples.Domain; namespace EasyWay.Samples.Events { public sealed class SampleEventHandler1 : DomainEventHandler { - public sealed override Task Handle(CreatedSampleAggragete domainEvent) + public override Task Handle(CreatedSampleAggragete domainEvent, Context context) { return Task.CompletedTask; } diff --git a/samples/EasyWay.Samples/Events/SampleEventHandler2.cs b/samples/EasyWay.Samples/Events/SampleEventHandler2.cs index df29dab..5bc6229 100644 --- a/samples/EasyWay.Samples/Events/SampleEventHandler2.cs +++ b/samples/EasyWay.Samples/Events/SampleEventHandler2.cs @@ -1,10 +1,11 @@ -using EasyWay.Samples.Domain; +using EasyWay.Events.DomainEvents; +using EasyWay.Samples.Domain; namespace EasyWay.Samples.Events { public sealed class SampleEventHandler2 : DomainEventHandler { - public sealed override Task Handle(CreatedSampleAggragete domainEvent) + public override Task Handle(CreatedSampleAggragete domainEvent, Context context) { return Task.CompletedTask; } diff --git a/source/EasyWay.EntityFrameworkCore/Internals/DomainEvents/DomainEventsAccessor.cs b/source/EasyWay.EntityFrameworkCore/Internals/DomainEvents/DomainEventsAccessor.cs index ba3842e..735e253 100644 --- a/source/EasyWay.EntityFrameworkCore/Internals/DomainEvents/DomainEventsAccessor.cs +++ b/source/EasyWay.EntityFrameworkCore/Internals/DomainEvents/DomainEventsAccessor.cs @@ -11,7 +11,7 @@ public DomainEventsAccessor(DbContext dbContext) _dbContext = dbContext; } - public IReadOnlyCollection GetAllDomainEvents() + public IReadOnlyCollection GetAllDomainEvents() { var domainEntities = _dbContext.ChangeTracker .Entries() diff --git a/source/EasyWay/DomainEvent.cs b/source/EasyWay/DomainEvent.cs index 916fc35..868c6a4 100644 --- a/source/EasyWay/DomainEvent.cs +++ b/source/EasyWay/DomainEvent.cs @@ -1,21 +1,10 @@ -using EasyWay.Internals.Clocks; -using EasyWay.Internals.GuidGenerators; - -namespace EasyWay +namespace EasyWay { /// /// Represents an event /// public abstract class DomainEvent { - internal Guid EventId { get; } - - internal DateTime OccurrenceOn { get; } - - protected DomainEvent() - { - EventId = GuidGenerator.New; - OccurrenceOn = InternalClock.UtcNow; - } + protected DomainEvent() { } } } diff --git a/source/EasyWay/DomainEventHandler.cs b/source/EasyWay/DomainEventHandler.cs index a61f751..9a944f6 100644 --- a/source/EasyWay/DomainEventHandler.cs +++ b/source/EasyWay/DomainEventHandler.cs @@ -1,4 +1,6 @@ -namespace EasyWay +using EasyWay.Events.DomainEvents; + +namespace EasyWay { /// /// Defines a handler for an event @@ -11,6 +13,6 @@ public abstract class DomainEventHandler /// Handles an event /// /// Event - public abstract Task Handle(TDomainEvent domainEvent); + public abstract Task Handle(TDomainEvent domainEvent, Context context); } } diff --git a/source/EasyWay/Entity.cs b/source/EasyWay/Entity.cs index a847b4a..3019acc 100644 --- a/source/EasyWay/Entity.cs +++ b/source/EasyWay/Entity.cs @@ -1,4 +1,5 @@ using EasyWay.Internals.BusinessRules; +using EasyWay.Internals.Clocks; using EasyWay.Internals.DomainEvents; using EasyWay.Internals.GuidGenerators; using System.Diagnostics.CodeAnalysis; @@ -9,9 +10,9 @@ public abstract class Entity : IEquatable, IEqualityComparer { internal Guid Id { get; private set; } = GuidGenerator.New; - private List _domainEvents = new List(); + private List _domainEvents = new List(); - internal IReadOnlyCollection DomainEvents => _domainEvents.AsReadOnly(); + internal IReadOnlyCollection DomainEvents => _domainEvents.AsReadOnly(); internal void ClearDomainEvents() => _domainEvents.Clear(); @@ -33,7 +34,16 @@ protected void Add(TDomainEvent domainEvent) throw new DomainEventCannotBeNullException(); } - _domainEvents.Add(domainEvent); + var domainEventContext = new DomainEventContext() + { + EventId = GuidGenerator.New, + AggragetRootId = Id, // TODO what when we have object graph + EntityId = Id, + OccurrenceOnUtc = InternalClock.UtcNow, + DomainEvent = domainEvent, + }; + + _domainEvents.Add(domainEventContext); } public static bool operator ==(Entity x, Entity y) => EntityEquals(x, y); diff --git a/source/EasyWay/Events/DomainEvents/Context.cs b/source/EasyWay/Events/DomainEvents/Context.cs new file mode 100644 index 0000000..4f31bd6 --- /dev/null +++ b/source/EasyWay/Events/DomainEvents/Context.cs @@ -0,0 +1,25 @@ +namespace EasyWay.Events.DomainEvents +{ + public sealed class Context + { + public Guid EventId { get; } + + public Guid AggragetRootId { get; } + + public Guid EntityId { get; } + + public DateTime OccurrenceOnUtc { get; } + + internal Context( + Guid eventId, + Guid aggragetRootId, + Guid entityId, + DateTime occurrenceOnUtc) + { + EventId = eventId; + AggragetRootId = aggragetRootId; + EntityId = entityId; + OccurrenceOnUtc = occurrenceOnUtc; + } + } +} diff --git a/source/EasyWay/Internals/DomainEvents/DomainEventBulkPublisher.cs b/source/EasyWay/Internals/DomainEvents/DomainEventBulkPublisher.cs index 947d46d..1c15e7c 100644 --- a/source/EasyWay/Internals/DomainEvents/DomainEventBulkPublisher.cs +++ b/source/EasyWay/Internals/DomainEvents/DomainEventBulkPublisher.cs @@ -9,12 +9,11 @@ public DomainEventBulkPublisher(IDomainEventPublisher domainEventPublisher) _domainEventPublisher = domainEventPublisher; } - public async Task Publish(IEnumerable domainEvents) - where TDomainEvent : DomainEvent + public async Task Publish(IEnumerable domainEventContexts) { - foreach (var domainEvent in domainEvents) + foreach (var domainEventContext in domainEventContexts) { - await _domainEventPublisher.Publish(domainEvent).ConfigureAwait(false); + await _domainEventPublisher.Publish(domainEventContext).ConfigureAwait(false); } } } diff --git a/source/EasyWay/Internals/DomainEvents/DomainEventContext.cs b/source/EasyWay/Internals/DomainEvents/DomainEventContext.cs new file mode 100644 index 0000000..b04f1d2 --- /dev/null +++ b/source/EasyWay/Internals/DomainEvents/DomainEventContext.cs @@ -0,0 +1,15 @@ +namespace EasyWay.Internals.DomainEvents +{ + internal sealed class DomainEventContext + { + internal required Guid EventId { get; init; } + + internal required Guid AggragetRootId { get; init; } + + internal required Guid EntityId { get; init; } + + internal required DateTime OccurrenceOnUtc { get; init; } + + internal required DomainEvent DomainEvent { get; init; } + } +} diff --git a/source/EasyWay/Internals/DomainEvents/DomainEventContextDispacher.cs b/source/EasyWay/Internals/DomainEvents/DomainEventContextDispacher.cs index d09d18d..6a06f48 100644 --- a/source/EasyWay/Internals/DomainEvents/DomainEventContextDispacher.cs +++ b/source/EasyWay/Internals/DomainEvents/DomainEventContextDispacher.cs @@ -16,9 +16,9 @@ public DomainEventContextDispacher( public async Task Dispach() { - var domainEvents = _context.GetAllDomainEvents(); + var domainEventContexts = _context.GetAllDomainEvents(); - if (!domainEvents.Any()) + if (!domainEventContexts.Any()) { return; } @@ -26,20 +26,20 @@ public async Task Dispach() _context.ClearAllDomainEvents(); await _publisher - .Publish(domainEvents) + .Publish(domainEventContexts) .ConfigureAwait(false); - domainEvents = _context.GetAllDomainEvents(); + domainEventContexts = _context.GetAllDomainEvents(); - while (domainEvents.Any()) + while (domainEventContexts.Any()) { _context.ClearAllDomainEvents(); await _publisher - .Publish(domainEvents) + .Publish(domainEventContexts) .ConfigureAwait(false); - domainEvents = _context.GetAllDomainEvents(); + domainEventContexts = _context.GetAllDomainEvents(); } } } diff --git a/source/EasyWay/Internals/DomainEvents/DomainEventPublisher.cs b/source/EasyWay/Internals/DomainEvents/DomainEventPublisher.cs index 3e360f9..0ce9f7d 100644 --- a/source/EasyWay/Internals/DomainEvents/DomainEventPublisher.cs +++ b/source/EasyWay/Internals/DomainEvents/DomainEventPublisher.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; +using EasyWay.Events.DomainEvents; +using Microsoft.Extensions.DependencyInjection; namespace EasyWay.Internals.DomainEvents { @@ -11,18 +12,25 @@ public DomainEventPublisher(IServiceProvider serviceProvider) _serviceProvider = serviceProvider; } - public async Task Publish(TEvent @event) - where TEvent : DomainEvent + public async Task Publish(DomainEventContext domainEventContext) { - var handlerType = typeof(DomainEventHandler<>).MakeGenericType(@event.GetType()); + var domainEvent = domainEventContext.DomainEvent; + + var handlerType = typeof(DomainEventHandler<>).MakeGenericType(domainEvent.GetType()); var eventHandlers = _serviceProvider.GetServices(handlerType); + var context = new Context( + eventId: domainEventContext.EntityId, + aggragetRootId: domainEventContext.AggragetRootId, + entityId: domainEventContext.EntityId, + occurrenceOnUtc: domainEventContext.OccurrenceOnUtc); + foreach (var eventHandler in eventHandlers) { await (Task)handlerType - .GetMethod(nameof(DomainEventHandler.Handle))? - .Invoke(eventHandler, new object[] { @event }); + .GetMethod(nameof(DomainEventHandler.Handle))? + .Invoke(eventHandler, new object[] { domainEvent, context }); } } } diff --git a/source/EasyWay/Internals/DomainEvents/IDomainEventBulkPublisher.cs b/source/EasyWay/Internals/DomainEvents/IDomainEventBulkPublisher.cs index 61fe380..574541f 100644 --- a/source/EasyWay/Internals/DomainEvents/IDomainEventBulkPublisher.cs +++ b/source/EasyWay/Internals/DomainEvents/IDomainEventBulkPublisher.cs @@ -2,7 +2,6 @@ { internal interface IDomainEventBulkPublisher { - Task Publish(IEnumerable domainEvents) - where TDomainEvent : DomainEvent; + Task Publish(IEnumerable domainEventContexts); } } diff --git a/source/EasyWay/Internals/DomainEvents/IDomainEventPublisher.cs b/source/EasyWay/Internals/DomainEvents/IDomainEventPublisher.cs index fcb9089..93e3d9c 100644 --- a/source/EasyWay/Internals/DomainEvents/IDomainEventPublisher.cs +++ b/source/EasyWay/Internals/DomainEvents/IDomainEventPublisher.cs @@ -2,7 +2,6 @@ { internal interface IDomainEventPublisher { - Task Publish(TEvent @event) - where TEvent : DomainEvent; + Task Publish(DomainEventContext domainEventContext); } } diff --git a/source/EasyWay/Internals/DomainEvents/IDomainEventsContext.cs b/source/EasyWay/Internals/DomainEvents/IDomainEventsContext.cs index 60a43eb..b22c03b 100644 --- a/source/EasyWay/Internals/DomainEvents/IDomainEventsContext.cs +++ b/source/EasyWay/Internals/DomainEvents/IDomainEventsContext.cs @@ -2,7 +2,7 @@ { internal interface IDomainEventsContext { - IReadOnlyCollection GetAllDomainEvents(); + IReadOnlyCollection GetAllDomainEvents(); void ClearAllDomainEvents(); } diff --git a/source/EasyWay/Internals/GuidGenerators/GuidGenerator.cs b/source/EasyWay/Internals/GuidGenerators/GuidGenerator.cs index 23718ed..16c90f0 100644 --- a/source/EasyWay/Internals/GuidGenerators/GuidGenerator.cs +++ b/source/EasyWay/Internals/GuidGenerators/GuidGenerator.cs @@ -12,6 +12,6 @@ internal static class GuidGenerator internal static void Reset() => _customId = null; - private static Guid Create() => Guid.CreateVersion7(InternalClock.UtcNow); + private static Guid Create() => Guid.CreateVersion7(InternalClock.TimeProvider.GetUtcNow()); } } diff --git a/tests/EasyWay.Tests/DomainEvents/DomainEventTests.cs b/tests/EasyWay.Tests/DomainEvents/DomainEventTests.cs deleted file mode 100644 index b5af5b7..0000000 --- a/tests/EasyWay.Tests/DomainEvents/DomainEventTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -using EasyWay.Internals.Clocks; -using Microsoft.Extensions.Time.Testing; - -namespace EasyWay.Tests.DomainEvents -{ - public sealed class DomainEventTests - { - internal sealed class TestDomainEvent : DomainEvent; - - [Fact(DisplayName = $"{nameof(DomainEvent)} should create with correct {nameof(DomainEvent.EventId)} and {nameof(DomainEvent.OccurrenceOn)}")] - public void Test() - { - // Arrange - var expectedDateTime = DateTime.UtcNow.AddMonths(-6); - var precision = TimeSpan.FromMilliseconds(50); - - InternalClock.Test(new FakeTimeProvider(expectedDateTime)); - - // Act - var domainEvent = new TestDomainEvent(); - - // Assert - Assert.NotEqual(Guid.Empty, domainEvent.EventId); - Assert.Equal(expectedDateTime, domainEvent.OccurrenceOn, precision); - } - } -} diff --git a/tests/EasyWay.Tests/Entities/AddDomainEventToEntity.cs b/tests/EasyWay.Tests/Entities/AddDomainEventToEntity.cs index be2de59..2bc8d34 100644 --- a/tests/EasyWay.Tests/Entities/AddDomainEventToEntity.cs +++ b/tests/EasyWay.Tests/Entities/AddDomainEventToEntity.cs @@ -19,7 +19,7 @@ public void AddDomainEvent() // Assert Assert.Equal(1, entity.DomainEvents.Count); - Assert.Equal(domainEvent, entity.DomainEvents.Single()); + Assert.Equal(domainEvent, entity.DomainEvents.Single().DomainEvent); } [Fact(DisplayName = $"When {nameof(DomainEvent)}s are added and then {nameof(Entity.ClearDomainEvents)} is executed, the domain event list should be empty")] diff --git a/tests/EasyWay.Tests/Internals/DomainEvents/DomainEventBulkPublisherTests.cs b/tests/EasyWay.Tests/Internals/DomainEvents/DomainEventBulkPublisherTests.cs deleted file mode 100644 index aac1282..0000000 --- a/tests/EasyWay.Tests/Internals/DomainEvents/DomainEventBulkPublisherTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using EasyWay.Internals.DomainEvents; -using Moq; - -namespace EasyWay.Tests.Internals.DomainEvents -{ - public sealed class DomainEventBulkPublisherTests - { - internal sealed class TestDomainEvent : DomainEvent; - - private readonly Mock _domainEventPublisherMock = new Mock(); - - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(10)] - public async Task DomainEventBulkPublisherShouldUsePublisherToPublishListOfDomainEvents(int numberOfDomainEvents) - { - // Arrange - _domainEventPublisherMock - .Setup(x => x.Publish(It.IsAny())) - .Returns(Task.CompletedTask); - - List events = new List(); - - for(int i = 0; i < numberOfDomainEvents; i++) - { - events.Add(new TestDomainEvent()); - } - - // Act - await new DomainEventBulkPublisher(_domainEventPublisherMock.Object).Publish(events); - - // Assert - _domainEventPublisherMock.Verify(x => x.Publish(It.IsAny()), Times.Exactly(numberOfDomainEvents)); - _domainEventPublisherMock.VerifyNoOtherCalls(); - } - } -} diff --git a/tests/EasyWay.Tests/Internals/DomainEvents/DomainEventContextDispacherTests.cs b/tests/EasyWay.Tests/Internals/DomainEvents/DomainEventContextDispacherTests.cs deleted file mode 100644 index d82a842..0000000 --- a/tests/EasyWay.Tests/Internals/DomainEvents/DomainEventContextDispacherTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using EasyWay.Internals.DomainEvents; -using Moq; - -namespace EasyWay.Tests.Internals.DomainEvents -{ - public sealed class DomainEventContextDispacherTests - { - internal sealed class TestDomainEvent : DomainEvent; - - private readonly Mock _publisherMock = new Mock(); - - private readonly Mock _contextMock = new Mock(); - - [Fact] - public async Task WhenContextReturnsEmptyList() - { - // Arrange - _contextMock - .Setup(x => x.GetAllDomainEvents()) - .Returns(new List()); - - // Act - await new DomainEventContextDispacher(_publisherMock.Object, _contextMock.Object).Dispach(); - - // Assert - _contextMock.Verify(x => x.GetAllDomainEvents(), Times.Once); - _contextMock.Verify(x => x.ClearAllDomainEvents(), Times.Never); - _publisherMock.Verify(x => x.Publish(It.IsAny>()), Times.Never); - - _contextMock.VerifyNoOtherCalls(); - _publisherMock.VerifyNoOtherCalls(); - } - - [Fact] - public async Task WhenContextReturnsSeveralTimesListWithDomainEvent() - { - // Arrange - var listWithDomainEvent = new List() { new TestDomainEvent() }; - var listWithoutDomainEvents = new List(); - - _contextMock - .SetupSequence(x => x.GetAllDomainEvents()) - .Returns(listWithDomainEvent) - .Returns(listWithDomainEvent) - .Returns(listWithDomainEvent) - .Returns(listWithDomainEvent) - .Returns(listWithDomainEvent) - .Returns(listWithoutDomainEvents); - - // Act - await new DomainEventContextDispacher(_publisherMock.Object, _contextMock.Object).Dispach(); - - // Assert - _contextMock.Verify(x => x.GetAllDomainEvents(), Times.Exactly(6)); - _contextMock.Verify(x => x.ClearAllDomainEvents(), Times.Exactly(5)); - _publisherMock.Verify(x => x.Publish(listWithDomainEvent), Times.Exactly(5)); - - _contextMock.VerifyNoOtherCalls(); - _publisherMock.VerifyNoOtherCalls(); - } - } -}