From ec54d2c2554e537eab50f65dc1e4868a5288325d Mon Sep 17 00:00:00 2001 From: farcasclaudiu Date: Mon, 10 Oct 2022 04:02:46 +0300 Subject: [PATCH] refactoring and integration tests --- PracticeCalendar.Api/Program.cs | 2 - .../appsettings.Development.json | 2 +- PracticeCalendar.Api/appsettings.json | 2 +- PracticeCalendar.Api/practicecalendar.sqlite | Bin 20480 -> 20480 bytes .../Events/AttendeeAddedEventNotification.cs | 24 ++++++++++++ .../Common/DomainEventBase.cs | 1 + .../Common/DomainEventNotification.cs | 14 +++++++ .../Common/Interfaces/IDomainEventService.cs | 7 ++++ .../Entities/PracticeEvent.cs | 9 +++++ .../Events/AttendeeAcceptEvent.cs | 17 ++++++++ .../Events/AttendeeDeclinedEvent.cs | 17 ++++++++ .../EventUpdateTitleAndDescriptionEvent.cs | 15 +++++++ .../ConfigureServices.cs | 2 + .../Persistence/ApplicationDbContext.cs | 37 ++++++++++++++++-- .../Services/DomainEventService.cs | 30 ++++++++++++++ 15 files changed, 172 insertions(+), 7 deletions(-) create mode 100644 PracticeCalendar.Application/PracticeEvents/Events/AttendeeAddedEventNotification.cs create mode 100644 PracticeCalendar.Domain/Common/DomainEventNotification.cs create mode 100644 PracticeCalendar.Domain/Common/Interfaces/IDomainEventService.cs create mode 100644 PracticeCalendar.Domain/Events/AttendeeAcceptEvent.cs create mode 100644 PracticeCalendar.Domain/Events/AttendeeDeclinedEvent.cs create mode 100644 PracticeCalendar.Domain/Events/EventUpdateTitleAndDescriptionEvent.cs create mode 100644 PracticeCalendar.Infrastructure/Services/DomainEventService.cs diff --git a/PracticeCalendar.Api/Program.cs b/PracticeCalendar.Api/Program.cs index 9c4d85c..fae36e4 100644 --- a/PracticeCalendar.Api/Program.cs +++ b/PracticeCalendar.Api/Program.cs @@ -27,8 +27,6 @@ namespace PracticeCalendar //inject infrastructure builder.Services.AddInfrastructureServices(builder.Configuration); - //mapster - var app = builder.Build(); diff --git a/PracticeCalendar.Api/appsettings.Development.json b/PracticeCalendar.Api/appsettings.Development.json index 0c208ae..a6e86ac 100644 --- a/PracticeCalendar.Api/appsettings.Development.json +++ b/PracticeCalendar.Api/appsettings.Development.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Information", + "Default": "Debug", "Microsoft.AspNetCore": "Warning" } } diff --git a/PracticeCalendar.Api/appsettings.json b/PracticeCalendar.Api/appsettings.json index 83a9a57..9464942 100644 --- a/PracticeCalendar.Api/appsettings.json +++ b/PracticeCalendar.Api/appsettings.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Information", + "Default": "Debug", "Microsoft.AspNetCore": "Warning" } }, diff --git a/PracticeCalendar.Api/practicecalendar.sqlite b/PracticeCalendar.Api/practicecalendar.sqlite index 5e6e9144cf8b9e9f0d453bfb00c7976d597c02ae..951caee88851776e155a46fbdb974f305d5aeaad 100644 GIT binary patch delta 410 zcmY+8Ci&B!i_el*I(vm-nvO=FK)gad`GLx5~3O+vmtj(Py~D zG1fu!L@nAPTfWN+xhD&vA50m~Vq^)vF|o`kQ6`E+JyDAd-27Q)#Te(QS-x4{DSOp& zWo^Gw^BRzhF;K|xi5J}C5 zj}}GIane%7#lf)TP%f%mw1mn+?F(uYEPW^!a)WMaQIDRXt6k~4rR!;+!|;o5eBlE< qyx|pHJmLX&2oc~4o#-P+m>W%T4kMNjD=AUN3X!PC>YCr(nEwNX0BB$U delta 142 zcmV;90CE3-paFoO0gxL36_Ff60Tr=eqz@Db4jBLsybpp8NDmGU&JK+ZQw|xk5g-8$ z0yqkj;T<`%R34WREeR3<11SMea$#e1X=7zYc4cmKa|#Ur000ONQ~(d_57rOC52_E7 w4}K464^*=eAUqF~R6np32oKQ!5Bm@G5AP4@591Hr57!UTvk@S<50lYPFNDS^?*IS* diff --git a/PracticeCalendar.Application/PracticeEvents/Events/AttendeeAddedEventNotification.cs b/PracticeCalendar.Application/PracticeEvents/Events/AttendeeAddedEventNotification.cs new file mode 100644 index 0000000..e736f4e --- /dev/null +++ b/PracticeCalendar.Application/PracticeEvents/Events/AttendeeAddedEventNotification.cs @@ -0,0 +1,24 @@ +using MediatR; +using PracticeCalendar.Domain.Common; +using PracticeCalendar.Domain.Events; +using PracticeCalendar.Domain.Interfaces; + +namespace PracticeCalendar.Application.PracticeEvents.Events +{ + public class AttendeeAddedEventNotification : INotificationHandler> + { + private readonly IEmailSender emailSender; + + public AttendeeAddedEventNotification(IEmailSender emailSender) + { + this.emailSender = emailSender; + } + + public async Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) + { + var sendTo = notification.DomainEvent.AddedAtendee.EmailAddress; + await emailSender.SendEmailAsync(sendTo, "system", + "You have been added to the event", "Confirmed you have been added to the event."); + } + } +} diff --git a/PracticeCalendar.Domain/Common/DomainEventBase.cs b/PracticeCalendar.Domain/Common/DomainEventBase.cs index 3ee56bc..a6a6a07 100644 --- a/PracticeCalendar.Domain/Common/DomainEventBase.cs +++ b/PracticeCalendar.Domain/Common/DomainEventBase.cs @@ -3,5 +3,6 @@ public class DomainEventBase { public DateTime EventDate { get; protected set; } = DateTime.UtcNow; + public bool IsPublished { get; set; } } } diff --git a/PracticeCalendar.Domain/Common/DomainEventNotification.cs b/PracticeCalendar.Domain/Common/DomainEventNotification.cs new file mode 100644 index 0000000..1281805 --- /dev/null +++ b/PracticeCalendar.Domain/Common/DomainEventNotification.cs @@ -0,0 +1,14 @@ +using MediatR; + +namespace PracticeCalendar.Domain.Common +{ + public class DomainEventNotification : INotification where TDomainEvent : DomainEventBase + { + public TDomainEvent DomainEvent { get; } + + public DomainEventNotification(TDomainEvent domainEvent) + { + DomainEvent = domainEvent; + } + } +} diff --git a/PracticeCalendar.Domain/Common/Interfaces/IDomainEventService.cs b/PracticeCalendar.Domain/Common/Interfaces/IDomainEventService.cs new file mode 100644 index 0000000..e7884fc --- /dev/null +++ b/PracticeCalendar.Domain/Common/Interfaces/IDomainEventService.cs @@ -0,0 +1,7 @@ +namespace PracticeCalendar.Domain.Common.Interfaces +{ + public interface IDomainEventService + { + Task Publish(DomainEventBase domainEvent); + } +} diff --git a/PracticeCalendar.Domain/Entities/PracticeEvent.cs b/PracticeCalendar.Domain/Entities/PracticeEvent.cs index 80c3b3d..4f823dd 100644 --- a/PracticeCalendar.Domain/Entities/PracticeEvent.cs +++ b/PracticeCalendar.Domain/Entities/PracticeEvent.cs @@ -41,6 +41,9 @@ namespace PracticeCalendar.Domain.Entities { this.Title = title; this.Description = description; + + var titleDescUpdatedEvent = new EventUpdateTitleAndDescriptionEvent(this); + base.RegisterDomainEvent(titleDescUpdatedEvent); } public void AttendeeAcceptEvent(int attendeeId) @@ -49,6 +52,9 @@ namespace PracticeCalendar.Domain.Entities if (attendee == null) throw new InvalidAttendeeException(attendeeId); attendee.SetIsAttending(true); + + var attendeeAcceptedEvent = new AttendeeAcceptedEvent(this, attendee); + base.RegisterDomainEvent(attendeeAcceptedEvent); } public void AttendeeDeclineEvent(int attendeeId) @@ -57,6 +63,9 @@ namespace PracticeCalendar.Domain.Entities if (attendee == null) throw new InvalidAttendeeException(attendeeId); attendee.SetIsAttending(false); + + var attendeeDeclinedEvent = new AttendeeDeclinedEvent(this, attendee); + base.RegisterDomainEvent(attendeeDeclinedEvent); } } } diff --git a/PracticeCalendar.Domain/Events/AttendeeAcceptEvent.cs b/PracticeCalendar.Domain/Events/AttendeeAcceptEvent.cs new file mode 100644 index 0000000..a367c8d --- /dev/null +++ b/PracticeCalendar.Domain/Events/AttendeeAcceptEvent.cs @@ -0,0 +1,17 @@ +using PracticeCalendar.Domain.Common; +using PracticeCalendar.Domain.Entities; + +namespace PracticeCalendar.Domain.Events +{ + public sealed class AttendeeAcceptedEvent : DomainEventBase + { + public AttendeeAcceptedEvent(PracticeEvent practiceEvent, Attendee attendee) + { + PracticeEvent = practiceEvent; + Attendee = attendee; + } + + public PracticeEvent PracticeEvent { get; } + public Attendee Attendee { get; } + } +} diff --git a/PracticeCalendar.Domain/Events/AttendeeDeclinedEvent.cs b/PracticeCalendar.Domain/Events/AttendeeDeclinedEvent.cs new file mode 100644 index 0000000..d66cec2 --- /dev/null +++ b/PracticeCalendar.Domain/Events/AttendeeDeclinedEvent.cs @@ -0,0 +1,17 @@ +using PracticeCalendar.Domain.Common; +using PracticeCalendar.Domain.Entities; + +namespace PracticeCalendar.Domain.Events +{ + public sealed class AttendeeDeclinedEvent : DomainEventBase + { + public AttendeeDeclinedEvent(PracticeEvent practiceEvent, Attendee attendee) + { + PracticeEvent = practiceEvent; + Attendee = attendee; + } + + public PracticeEvent PracticeEvent { get; } + public Attendee Attendee { get; } + } +} diff --git a/PracticeCalendar.Domain/Events/EventUpdateTitleAndDescriptionEvent.cs b/PracticeCalendar.Domain/Events/EventUpdateTitleAndDescriptionEvent.cs new file mode 100644 index 0000000..c7339c7 --- /dev/null +++ b/PracticeCalendar.Domain/Events/EventUpdateTitleAndDescriptionEvent.cs @@ -0,0 +1,15 @@ +using PracticeCalendar.Domain.Common; +using PracticeCalendar.Domain.Entities; + +namespace PracticeCalendar.Domain.Events +{ + public sealed class EventUpdateTitleAndDescriptionEvent : DomainEventBase + { + public EventUpdateTitleAndDescriptionEvent(PracticeEvent practiceEvent) + { + PracticeEvent = practiceEvent; + } + + public PracticeEvent PracticeEvent { get; } + } +} diff --git a/PracticeCalendar.Infrastructure/ConfigureServices.cs b/PracticeCalendar.Infrastructure/ConfigureServices.cs index 5a67acc..a01c34a 100644 --- a/PracticeCalendar.Infrastructure/ConfigureServices.cs +++ b/PracticeCalendar.Infrastructure/ConfigureServices.cs @@ -5,6 +5,7 @@ using PracticeCalendar.Domain.Common.Interfaces; using PracticeCalendar.Domain.Interfaces; using PracticeCalendar.Infrastructure.Notification; using PracticeCalendar.Infrastructure.Persistence; +using PracticeCalendar.Infrastructure.Services; namespace PracticeCalendar.Infrastructure { @@ -17,6 +18,7 @@ namespace PracticeCalendar.Infrastructure services.AddDbContext(connectionString); services.AddTransient(); + services.AddTransient(); services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>)); diff --git a/PracticeCalendar.Infrastructure/Persistence/ApplicationDbContext.cs b/PracticeCalendar.Infrastructure/Persistence/ApplicationDbContext.cs index dca7d7f..8962c25 100644 --- a/PracticeCalendar.Infrastructure/Persistence/ApplicationDbContext.cs +++ b/PracticeCalendar.Infrastructure/Persistence/ApplicationDbContext.cs @@ -1,4 +1,6 @@ using Microsoft.EntityFrameworkCore; +using PracticeCalendar.Domain.Common; +using PracticeCalendar.Domain.Common.Interfaces; using PracticeCalendar.Domain.Entities; using System.Reflection; @@ -6,9 +8,15 @@ namespace PracticeCalendar.Infrastructure.Persistence { public class ApplicationDbContext : DbContext { - public ApplicationDbContext(DbContextOptions options) + private readonly IDomainEventService domainEventService; + + public DbSet Atendees => Set(); + public DbSet PracticeEvents => Set(); + + public ApplicationDbContext(DbContextOptions options, IDomainEventService domainEventService) : base(options) { + this.domainEventService = domainEventService; } protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -17,7 +25,30 @@ namespace PracticeCalendar.Infrastructure.Persistence modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); } - public DbSet Atendees => Set(); - public DbSet PracticeEvents => Set(); + + + public override async Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) + { + var events = ChangeTracker.Entries() + .Select(x => x.Entity.DomainEvents) + .SelectMany(x => x) + .Where(domainEvent => !domainEvent.IsPublished) + .ToArray(); + + var result = await base.SaveChangesAsync(cancellationToken); + + await DispatchEvents(events); + + return result; + } + + private async Task DispatchEvents(DomainEventBase[] events) + { + foreach (var @event in events) + { + @event.IsPublished = true; + await domainEventService.Publish(@event); + } + } } } diff --git a/PracticeCalendar.Infrastructure/Services/DomainEventService.cs b/PracticeCalendar.Infrastructure/Services/DomainEventService.cs new file mode 100644 index 0000000..126f000 --- /dev/null +++ b/PracticeCalendar.Infrastructure/Services/DomainEventService.cs @@ -0,0 +1,30 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using PracticeCalendar.Domain.Common; +using PracticeCalendar.Domain.Common.Interfaces; + +namespace PracticeCalendar.Infrastructure.Services +{ + public class DomainEventService : IDomainEventService + { + private readonly ILogger logger; + private readonly IPublisher mediator; + + public DomainEventService(ILogger logger, IPublisher mediator) + { + this.logger = logger; + this.mediator = mediator; + } + public async Task Publish(DomainEventBase domainEvent) + { + logger.LogInformation("Publishing domain event. Event - {event}", domainEvent.GetType().Name); + await mediator.Publish(GetNotificationCorrespondingToDomainEvent(domainEvent)); + } + + private INotification GetNotificationCorrespondingToDomainEvent(DomainEventBase domainEvent) + { + return (INotification)Activator.CreateInstance( + typeof(DomainEventNotification<>).MakeGenericType(domainEvent.GetType()), domainEvent)!; + } + } +}