feat(backend): Implement Domain Events infrastructure and handlers
Add complete domain events dispatching infrastructure and critical event handlers for Identity module. Changes: - Added IMediator injection to IdentityDbContext - Implemented SaveChangesAsync override to dispatch domain events before persisting - Made DomainEvent base class implement INotification (added MediatR.Contracts dependency) - Created 3 new domain events: UserRoleAssignedEvent, UserRemovedFromTenantEvent, UserLoggedInEvent - Implemented 4 event handlers with structured logging: - UserRoleAssignedEventHandler (audit log, cache invalidation placeholder) - UserRemovedFromTenantEventHandler (notification placeholder) - UserLoggedInEventHandler (login tracking placeholder) - TenantCreatedEventHandler (welcome email placeholder) - Updated unit tests to inject mock IMediator into IdentityDbContext Technical Details: - Domain events are now published via MediatR within the same transaction - Events are dispatched BEFORE SaveChangesAsync to ensure atomicity - Event handlers auto-registered by MediatR assembly scanning - All handlers include structured logging for observability Next Steps (Phase 3): - Update command handlers to raise new events (UserLoggedInEvent, UserRoleAssignedEvent) - Add event raising logic to User/Tenant aggregates - Implement audit logging persistence (currently just logging) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ using ColaFlow.Modules.Identity.Domain.Aggregates.Users;
|
||||
using ColaFlow.Modules.Identity.Infrastructure.Persistence;
|
||||
using ColaFlow.Modules.Identity.Infrastructure.Services;
|
||||
using FluentAssertions;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moq;
|
||||
|
||||
@@ -11,6 +12,7 @@ namespace ColaFlow.Modules.Identity.Infrastructure.Tests.Persistence;
|
||||
public class GlobalQueryFilterTests : IDisposable
|
||||
{
|
||||
private readonly Mock<ITenantContext> _mockTenantContext;
|
||||
private readonly Mock<IMediator> _mockMediator;
|
||||
private readonly IdentityDbContext _context;
|
||||
|
||||
public GlobalQueryFilterTests()
|
||||
@@ -20,7 +22,8 @@ public class GlobalQueryFilterTests : IDisposable
|
||||
.Options;
|
||||
|
||||
_mockTenantContext = new Mock<ITenantContext>();
|
||||
_context = new IdentityDbContext(options, _mockTenantContext.Object);
|
||||
_mockMediator = new Mock<IMediator>();
|
||||
_context = new IdentityDbContext(options, _mockTenantContext.Object, _mockMediator.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -39,7 +42,8 @@ public class GlobalQueryFilterTests : IDisposable
|
||||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
|
||||
using var context = new IdentityDbContext(options, mockTenantContext.Object);
|
||||
var mockMediator = new Mock<IMediator>();
|
||||
using var context = new IdentityDbContext(options, mockTenantContext.Object, mockMediator.Object);
|
||||
|
||||
var user1 = User.CreateLocal(
|
||||
tenant1Id,
|
||||
@@ -81,7 +85,8 @@ public class GlobalQueryFilterTests : IDisposable
|
||||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
|
||||
using var context = new IdentityDbContext(options, mockTenantContext.Object);
|
||||
var mockMediator = new Mock<IMediator>();
|
||||
using var context = new IdentityDbContext(options, mockTenantContext.Object, mockMediator.Object);
|
||||
|
||||
var user1 = User.CreateLocal(tenant1Id, Email.Create("admin@tenant1.com"), "pass", FullName.Create("Admin One"));
|
||||
var user2 = User.CreateLocal(tenant2Id, Email.Create("admin@tenant2.com"), "pass", FullName.Create("Admin Two"));
|
||||
@@ -111,7 +116,8 @@ public class GlobalQueryFilterTests : IDisposable
|
||||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
|
||||
using var context = new IdentityDbContext(options, mockTenantContext.Object);
|
||||
var mockMediator = new Mock<IMediator>();
|
||||
using var context = new IdentityDbContext(options, mockTenantContext.Object, mockMediator.Object);
|
||||
|
||||
var user1 = User.CreateLocal(tenant1Id, Email.Create("user1@test.com"), "pass", FullName.Create("User One"));
|
||||
var user2 = User.CreateLocal(tenant2Id, Email.Create("user2@test.com"), "pass", FullName.Create("User Two"));
|
||||
@@ -142,7 +148,8 @@ public class GlobalQueryFilterTests : IDisposable
|
||||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
|
||||
using var context = new IdentityDbContext(options, mockTenantContext.Object);
|
||||
var mockMediator = new Mock<IMediator>();
|
||||
using var context = new IdentityDbContext(options, mockTenantContext.Object, mockMediator.Object);
|
||||
|
||||
var user1 = User.CreateLocal(tenant1Id, Email.Create("john@tenant1.com"), "pass", FullName.Create("John Doe"));
|
||||
var user2 = User.CreateLocal(tenant2Id, Email.Create("jane@tenant2.com"), "pass", FullName.Create("Jane Doe"));
|
||||
|
||||
@@ -3,6 +3,7 @@ using ColaFlow.Modules.Identity.Infrastructure.Persistence;
|
||||
using ColaFlow.Modules.Identity.Infrastructure.Persistence.Repositories;
|
||||
using ColaFlow.Modules.Identity.Infrastructure.Services;
|
||||
using FluentAssertions;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moq;
|
||||
|
||||
@@ -22,7 +23,9 @@ public class TenantRepositoryTests : IDisposable
|
||||
var mockTenantContext = new Mock<ITenantContext>();
|
||||
mockTenantContext.Setup(x => x.IsSet).Returns(false);
|
||||
|
||||
_context = new IdentityDbContext(options, mockTenantContext.Object);
|
||||
var mockMediator = new Mock<IMediator>();
|
||||
|
||||
_context = new IdentityDbContext(options, mockTenantContext.Object, mockMediator.Object);
|
||||
_repository = new TenantRepository(_context);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user