# Domain Events Implementation Analysis & Plan **Date:** 2025-11-03 **Module:** Identity Module (ColaFlow.Modules.Identity) **Status:** Gap Analysis Complete - Implementation Required --- ## Executive Summary ### Current State The Identity module has **partial domain events implementation**: - ✅ Domain event infrastructure exists (base classes, AggregateRoot pattern) - ✅ **11 domain events defined** in the domain layer - ✅ Domain events are being **raised** in aggregates (Tenant, User) - ❌ **Domain events are NOT being dispatched** (events are raised but never published) - ❌ **No domain event handlers** implemented - ❌ Repository pattern calls `SaveChangesAsync` directly, bypassing event dispatching ### Critical Finding **Domain events are being collected but never published!** This means: - Events like `TenantCreated`, `UserCreated`, `UserRoleAssigned` are raised but silently discarded - No audit logging, no side effects, no cross-module notifications - The infrastructure is 80% complete but missing the final critical piece ### Recommended Action **Immediate implementation required** - Domain events are foundational for: - Audit logging (required for compliance) - Cross-module communication (required for modularity) - Side effects (email notifications, cache invalidation, etc.) - Event sourcing (future requirement) --- ## 1. Current State Assessment ### 1.1 Domain Event Infrastructure (✅ Complete) #### Base Classes **`ColaFlow.Shared.Kernel.Events.DomainEvent`** ```csharp public abstract record DomainEvent { public Guid EventId { get; init; } = Guid.NewGuid(); public DateTime OccurredOn { get; init; } = DateTime.UtcNow; } ``` - ✅ Properly designed as record (immutable) - ✅ Auto-generates EventId and timestamp - ✅ Follows best practices **`ColaFlow.Shared.Kernel.Common.AggregateRoot`** ```csharp public abstract class AggregateRoot : Entity { private readonly List _domainEvents = new(); public IReadOnlyCollection DomainEvents => _domainEvents.AsReadOnly(); protected void AddDomainEvent(DomainEvent domainEvent) { _domainEvents.Add(domainEvent); } public void ClearDomainEvents() { _domainEvents.Clear(); } } ``` - ✅ Encapsulates domain events collection - ✅ Provides AddDomainEvent method for aggregates - ✅ Provides ClearDomainEvents for cleanup after dispatching - ✅ Follows DDD best practices (encapsulation) ### 1.2 Domain Events Defined (✅ Complete) #### Tenant Events (7 events) | Event | File | Raised In | Purpose | |-------|------|-----------|---------| | `TenantCreatedEvent` | `Tenants/Events/` | `Tenant.Create()` | New tenant registration | | `TenantActivatedEvent` | `Tenants/Events/` | `Tenant.Activate()` | Tenant reactivation | | `TenantSuspendedEvent` | `Tenants/Events/` | `Tenant.Suspend()` | Tenant suspension | | `TenantCancelledEvent` | `Tenants/Events/` | `Tenant.Cancel()` | Tenant cancellation | | `TenantPlanUpgradedEvent` | `Tenants/Events/` | `Tenant.UpgradePlan()` | Plan upgrade | | `SsoConfiguredEvent` | `Tenants/Events/` | `Tenant.ConfigureSso()` | SSO setup | | `SsoDisabledEvent` | `Tenants/Events/` | `Tenant.DisableSso()` | SSO removal | **Example:** ```csharp public sealed record TenantCreatedEvent(Guid TenantId, string Slug) : DomainEvent; ``` #### User Events (4 events) | Event | File | Raised In | Purpose | |-------|------|-----------|---------| | `UserCreatedEvent` | `Users/Events/` | `User.CreateLocal()` | Local user registration | | `UserCreatedFromSsoEvent` | `Users/Events/` | `User.CreateFromSso()` | SSO user registration | | `UserPasswordChangedEvent` | `Users/Events/` | `User.UpdatePassword()` | Password change | | `UserSuspendedEvent` | `Users/Events/` | `User.Suspend()` | User suspension | **Example:** ```csharp public sealed record UserCreatedEvent( Guid UserId, string Email, TenantId TenantId ) : DomainEvent; ``` ### 1.3 Event Dispatching Infrastructure (❌ Missing in Identity Module) #### ProjectManagement Module (Reference Implementation) **`ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence.UnitOfWork`** ```csharp public async Task SaveChangesAsync(CancellationToken cancellationToken = default) { // Dispatch domain events before saving await DispatchDomainEventsAsync(cancellationToken); // Save changes to database return await _context.SaveChangesAsync(cancellationToken); } private async Task DispatchDomainEventsAsync(CancellationToken cancellationToken) { // Get all entities with domain events var domainEntities = _context.ChangeTracker .Entries() .Where(x => x.Entity.DomainEvents.Any()) .Select(x => x.Entity) .ToList(); // Get all domain events var domainEvents = domainEntities .SelectMany(x => x.DomainEvents) .ToList(); // Clear domain events from entities domainEntities.ForEach(entity => entity.ClearDomainEvents()); // TODO: Dispatch domain events to handlers // This will be implemented when we add MediatR await Task.CompletedTask; } ``` **Status:** ✅ Infrastructure exists in ProjectManagement module, ❌ Not implemented in Identity module #### Identity Module (Current Implementation) **`IdentityDbContext`** - ❌ No `SaveChangesAsync` override - ❌ No domain event dispatching - ❌ No UnitOfWork pattern **Repositories (TenantRepository, UserRepository, etc.)** ```csharp public async Task AddAsync(Tenant tenant, CancellationToken cancellationToken = default) { await _context.Tenants.AddAsync(tenant, cancellationToken); await _context.SaveChangesAsync(cancellationToken); // ❌ Direct call, bypasses events } ``` **Problem:** Repositories call `DbContext.SaveChangesAsync()` directly, so domain events are never dispatched. ### 1.4 Domain Event Handlers (❌ Missing) **Current State:** - ❌ No `INotificationHandler` implementations - ❌ No event handler folder structure - ❌ MediatR registered in Application layer but not configured for domain events **Expected Structure (Not Present):** ``` ColaFlow.Modules.Identity.Application/ ├── EventHandlers/ │ ├── Tenants/ │ │ ├── TenantCreatedEventHandler.cs ❌ Missing │ │ └── TenantPlanUpgradedEventHandler.cs ❌ Missing │ └── Users/ │ ├── UserCreatedEventHandler.cs ❌ Missing │ └── UserSuspendedEventHandler.cs ❌ Missing ``` --- ## 2. Gap Analysis ### 2.1 What's Working | Component | Status | Notes | |-----------|--------|-------| | Domain Event Base Class | ✅ Complete | Well-designed record with EventId and timestamp | | AggregateRoot Pattern | ✅ Complete | Proper encapsulation of domain events collection | | Domain Events Defined | ✅ Complete | 11 events defined and raised in aggregates | | MediatR Registration | ✅ Complete | MediatR registered in Application layer | ### 2.2 What's Missing | Component | Status | Impact | Priority | |-----------|--------|--------|----------| | **Event Dispatching in DbContext** | ❌ Missing | HIGH - Events never published | **CRITICAL** | | **UnitOfWork Pattern** | ❌ Missing | HIGH - No transaction boundary for events | **CRITICAL** | | **Domain Event Handlers** | ❌ Missing | HIGH - No side effects, no audit logging | **HIGH** | | **MediatR Integration for Events** | ❌ Missing | HIGH - Events not routed to handlers | **CRITICAL** | | **Repository Pattern Refactoring** | ❌ Missing | MEDIUM - Repositories bypass UnitOfWork | **HIGH** | ### 2.3 Missing Events (Day 6+ Features) Based on Day 4-6 implementation, these events should exist but don't: | Event | Scenario | Raised In | Priority | |-------|----------|-----------|----------| | `UserLoggedInEvent` | Login success | LoginCommandHandler | HIGH | | `UserLoginFailedEvent` | Login failure | LoginCommandHandler | MEDIUM | | `RefreshTokenGeneratedEvent` | Token refresh | RefreshTokenService | MEDIUM | | `RefreshTokenRevokedEvent` | Token revocation | RefreshTokenService | MEDIUM | | `UserRoleAssignedEvent` | Role assignment | AssignUserRoleCommand | **HIGH** | | `UserRoleUpdatedEvent` | Role change | AssignUserRoleCommand | **HIGH** | | `UserRemovedFromTenantEvent` | User removal | RemoveUserFromTenantCommand | **HIGH** | | `UserTokensRevokedEvent` | Token revocation | RemoveUserFromTenantCommand | MEDIUM | --- ## 3. Recommended Architecture ### 3.1 Domain Event Dispatching Pattern **Option A: Dispatch in DbContext.SaveChangesAsync (Recommended)** **Pros:** - ✅ Centralized event dispatching - ✅ Consistent across all operations - ✅ Events dispatched within transaction boundary - ✅ Follows EF Core best practices **Cons:** - ⚠️ Requires overriding `SaveChangesAsync` in each module's DbContext - ⚠️ Tight coupling to EF Core **Implementation:** ```csharp // IdentityDbContext.cs public class IdentityDbContext : DbContext { private readonly IMediator _mediator; public IdentityDbContext( DbContextOptions options, ITenantContext tenantContext, IMediator mediator) // ✅ Inject MediatR : base(options) { _tenantContext = tenantContext; _mediator = mediator; } public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) { // Dispatch domain events BEFORE saving await DispatchDomainEventsAsync(cancellationToken); // Save changes to database return await base.SaveChangesAsync(cancellationToken); } private async Task DispatchDomainEventsAsync(CancellationToken cancellationToken) { // Get all aggregate roots with domain events var domainEntities = ChangeTracker .Entries() .Where(x => x.Entity.DomainEvents.Any()) .Select(x => x.Entity) .ToList(); // Get all domain events var domainEvents = domainEntities .SelectMany(x => x.DomainEvents) .ToList(); // Clear domain events from entities domainEntities.ForEach(entity => entity.ClearDomainEvents()); // Dispatch events to handlers via MediatR foreach (var domainEvent in domainEvents) { await _mediator.Publish(domainEvent, cancellationToken); } } } ``` **Option B: Dispatch in UnitOfWork (Alternative)** **Pros:** - ✅ Decouples from DbContext - ✅ Testable without EF Core - ✅ Follows Clean Architecture more strictly **Cons:** - ⚠️ Requires UnitOfWork pattern implementation - ⚠️ More boilerplate code - ⚠️ Repositories must use UnitOfWork instead of direct SaveChangesAsync **Not recommended for now** - Option A is simpler and sufficient for current needs. ### 3.2 MediatR Configuration **Current Configuration:** ```csharp // Application/DependencyInjection.cs public static IServiceCollection AddIdentityApplication(this IServiceCollection services) { // MediatR services.AddMediatR(config => { config.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly); }); // FluentValidation services.AddValidatorsFromAssembly(typeof(DependencyInjection).Assembly); return services; } ``` **Status:** ✅ Already configured for commands/queries, will automatically handle domain events **How MediatR Works:** 1. Domain events inherit from `DomainEvent` (which is a record) 2. Event handlers implement `INotificationHandler` 3. `_mediator.Publish(event)` dispatches to ALL handlers **Key Point:** MediatR treats domain events as notifications (pub-sub pattern), so multiple handlers can react to the same event. ### 3.3 Domain Event Handler Pattern **Handler Structure:** ```csharp // Application/EventHandlers/Users/UserCreatedEventHandler.cs public class UserCreatedEventHandler : INotificationHandler { private readonly IAuditLogRepository _auditLogRepository; private readonly ILogger _logger; public UserCreatedEventHandler( IAuditLogRepository auditLogRepository, ILogger logger) { _auditLogRepository = auditLogRepository; _logger = logger; } public async Task Handle(UserCreatedEvent notification, CancellationToken cancellationToken) { _logger.LogInformation( "User {UserId} created in tenant {TenantId}", notification.UserId, notification.TenantId); // Example: Log to audit trail var auditLog = AuditLog.Create( entityType: "User", entityId: notification.UserId, action: "Created", performedBy: notification.UserId, // Self-registration timestamp: notification.OccurredOn); await _auditLogRepository.AddAsync(auditLog, cancellationToken); } } ``` **Multiple Handlers for Same Event:** ```csharp // Application/EventHandlers/Users/UserCreatedEmailNotificationHandler.cs public class UserCreatedEmailNotificationHandler : INotificationHandler { private readonly IEmailService _emailService; public async Task Handle(UserCreatedEvent notification, CancellationToken cancellationToken) { // Send welcome email await _emailService.SendWelcomeEmailAsync( notification.Email, notification.UserId, cancellationToken); } } ``` **Key Benefits:** - ✅ Single Responsibility Principle (each handler does one thing) - ✅ Decoupled side effects (audit, email, cache, etc.) - ✅ Easy to add new handlers without modifying existing code --- ## 4. Implementation Plan ### Option A: Implement Now (Recommended) **Reasoning:** - Domain events are fundamental to the architecture - Required for Day 6 features (role management audit) - Critical for audit logging and compliance - Relatively small implementation effort (2-4 hours) **Timeline:** Day 6 (Today) - Implement alongside role management features --- ### Option B: Implement in Day 7 **Reasoning:** - Can defer if Day 6 deadline is tight - Focus on completing role management first - Implement events as cleanup/refactoring task **Timeline:** Day 7 (Tomorrow) - Dedicated domain events implementation day --- ### Option C: Incremental Implementation **Reasoning:** - Implement infrastructure first (dispatching in DbContext) - Add event handlers incrementally as needed - Start with critical events (UserCreated, TenantCreated, UserRoleAssigned) **Timeline:** Days 6-8 - Spread across multiple days --- ### ✅ RECOMMENDED: Option C (Incremental Implementation) **Phase 1: Infrastructure (Day 6, ~1 hour)** 1. Override `SaveChangesAsync` in `IdentityDbContext` 2. Implement `DispatchDomainEventsAsync` method 3. Inject `IMediator` into DbContext 4. Test that events are being published (add logging) **Phase 2: Critical Event Handlers (Day 6-7, ~2 hours)** 1. `UserCreatedEventHandler` - Audit logging 2. `TenantCreatedEventHandler` - Audit logging 3. `UserRoleAssignedEventHandler` - Audit logging + cache invalidation **Phase 3: Additional Event Handlers (Day 7-8, ~2 hours)** 1. `UserLoggedInEvent` + handler - Login audit trail 2. `RefreshTokenRevokedEvent` + handler - Security audit 3. `TenantSuspendedEvent` + handler - Notify users, revoke tokens **Phase 4: Future Events (Day 9+)** 1. Email verification events 2. Password reset events 3. SSO events 4. Cross-module integration events --- ## 5. Step-by-Step Implementation Guide ### Step 1: Add Domain Event Dispatching to DbContext **File:** `src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/IdentityDbContext.cs` **Changes:** ```csharp using ColaFlow.Shared.Kernel.Common; using MediatR; public class IdentityDbContext : DbContext { private readonly ITenantContext _tenantContext; private readonly IMediator _mediator; // ✅ Add public IdentityDbContext( DbContextOptions options, ITenantContext tenantContext, IMediator mediator) // ✅ Add : base(options) { _tenantContext = tenantContext; _mediator = mediator; // ✅ Add } // ✅ Add SaveChangesAsync override public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) { await DispatchDomainEventsAsync(cancellationToken); return await base.SaveChangesAsync(cancellationToken); } // ✅ Add DispatchDomainEventsAsync method private async Task DispatchDomainEventsAsync(CancellationToken cancellationToken) { var domainEntities = ChangeTracker .Entries() .Where(x => x.Entity.DomainEvents.Any()) .Select(x => x.Entity) .ToList(); var domainEvents = domainEntities .SelectMany(x => x.DomainEvents) .ToList(); domainEntities.ForEach(entity => entity.ClearDomainEvents()); foreach (var domainEvent in domainEvents) { await _mediator.Publish(domainEvent, cancellationToken); } } } ``` **Estimated Time:** 15 minutes --- ### Step 2: Create Missing Domain Events **File:** `src/Modules/Identity/ColaFlow.Modules.Identity.Domain/Aggregates/Users/Events/UserRoleAssignedEvent.cs` ```csharp using ColaFlow.Shared.Kernel.Events; using ColaFlow.Modules.Identity.Domain.Aggregates.Tenants; namespace ColaFlow.Modules.Identity.Domain.Aggregates.Users.Events; public sealed record UserRoleAssignedEvent( Guid UserId, TenantId TenantId, TenantRole Role, Guid AssignedBy ) : DomainEvent; ``` **File:** `src/Modules/Identity/ColaFlow.Modules.Identity.Domain/Aggregates/Users/Events/UserRemovedFromTenantEvent.cs` ```csharp public sealed record UserRemovedFromTenantEvent( Guid UserId, TenantId TenantId, Guid RemovedBy ) : DomainEvent; ``` **File:** `src/Modules/Identity/ColaFlow.Modules.Identity.Domain/Aggregates/Users/Events/UserLoggedInEvent.cs` ```csharp public sealed record UserLoggedInEvent( Guid UserId, TenantId TenantId, string IpAddress, string UserAgent ) : DomainEvent; ``` **Estimated Time:** 30 minutes --- ### Step 3: Raise Events in Aggregates **Update:** `AssignUserRoleCommandHandler` to raise `UserRoleAssignedEvent` ```csharp // AssignUserRoleCommandHandler.cs public async Task Handle(AssignUserRoleCommand request, CancellationToken cancellationToken) { // ... existing validation logic ... // Create or update role assignment var userTenantRole = UserTenantRole.Create(userId, tenantId, request.Role); await _userTenantRoleRepository.AddOrUpdateAsync(userTenantRole, cancellationToken); // ✅ Raise domain event (if we make UserTenantRole an AggregateRoot) // OR raise event from User aggregate var user = await _userRepository.GetByIdAsync(userId, cancellationToken); if (user != null) { user.AddDomainEvent(new UserRoleAssignedEvent( userId.Value, tenantId, request.Role, currentUserId)); // From JWT claims } return Unit.Value; } ``` **Estimated Time:** 1 hour (refactor command handlers) --- ### Step 4: Create Event Handlers **File:** `src/Modules/Identity/ColaFlow.Modules.Identity.Application/EventHandlers/Users/UserRoleAssignedEventHandler.cs` ```csharp using ColaFlow.Modules.Identity.Domain.Aggregates.Users.Events; using MediatR; using Microsoft.Extensions.Logging; namespace ColaFlow.Modules.Identity.Application.EventHandlers.Users; public class UserRoleAssignedEventHandler : INotificationHandler { private readonly ILogger _logger; public UserRoleAssignedEventHandler(ILogger logger) { _logger = logger; } public Task Handle(UserRoleAssignedEvent notification, CancellationToken cancellationToken) { _logger.LogInformation( "User {UserId} assigned role {Role} in tenant {TenantId} by user {AssignedBy}", notification.UserId, notification.Role, notification.TenantId, notification.AssignedBy); // TODO: Add to audit log // TODO: Invalidate user's cached permissions // TODO: Send notification to user return Task.CompletedTask; } } ``` **Estimated Time:** 30 minutes per handler (create 3-5 handlers) --- ### Step 5: Test Domain Events **Test Script:** ```csharp // Integration test [Fact] public async Task AssignUserRole_Should_Raise_UserRoleAssignedEvent() { // Arrange var command = new AssignUserRoleCommand(userId, tenantId, TenantRole.Admin); // Act await _mediator.Send(command); // Assert // Verify event was raised and handled _mockLogger.Verify( x => x.LogInformation( It.Is(s => s.Contains("User") && s.Contains("assigned role")), It.IsAny()), Times.Once); } ``` **Manual Test:** 1. Assign a role to a user via API 2. Check logs for "User {UserId} assigned role {Role}" 3. Verify event was published and handler executed **Estimated Time:** 30 minutes --- ## 6. Priority Assessment ### Critical Events (Implement in Day 6) | Event | Scenario | Handler Actions | Priority | |-------|----------|----------------|----------| | `UserRoleAssignedEvent` | Role assignment | Audit log, cache invalidation, notification | **CRITICAL** | | `UserRemovedFromTenantEvent` | User removal | Audit log, revoke tokens, cleanup | **CRITICAL** | | `TenantCreatedEvent` | Tenant registration | Audit log, send welcome email | **HIGH** | | `UserCreatedEvent` | User registration | Audit log, send welcome email | **HIGH** | ### High Priority Events (Implement in Day 7) | Event | Scenario | Handler Actions | Priority | |-------|----------|----------------|----------| | `UserLoggedInEvent` | Login success | Audit log, update LastLoginAt | **HIGH** | | `RefreshTokenRevokedEvent` | Token revocation | Audit log, security notification | **HIGH** | | `TenantSuspendedEvent` | Tenant suspension | Notify users, revoke all tokens | **HIGH** | | `UserSuspendedEvent` | User suspension | Revoke tokens, audit log | **HIGH** | ### Medium Priority Events (Implement in Day 8+) | Event | Scenario | Handler Actions | Priority | |-------|----------|----------------|----------| | `UserPasswordChangedEvent` | Password change | Audit log, revoke old tokens, email notification | MEDIUM | | `TenantPlanUpgradedEvent` | Plan upgrade | Update limits, audit log, send invoice | MEDIUM | | `SsoConfiguredEvent` | SSO setup | Audit log, notify admins | MEDIUM | --- ## 7. Risks & Mitigation ### Risk 1: Performance Impact **Risk:** Dispatching many events could slow down SaveChangesAsync **Mitigation:** - Domain events are published in-process (fast) - Consider async background processing for non-critical events (future) - Monitor performance with logging ### Risk 2: Event Handler Failures **Risk:** Event handler throws exception, entire transaction rolls back **Mitigation:** - Wrap event dispatching in try-catch - Log exceptions but don't fail transaction - Consider eventual consistency for non-critical handlers ### Risk 3: Event Ordering **Risk:** Events might be processed out of order **Mitigation:** - Events are dispatched in the order they were raised (in single transaction) - Use OccurredOn timestamp for ordering if needed - Consider event sequence numbers (future) ### Risk 4: Missing Events **Risk:** Forgetting to raise events in new features **Mitigation:** - Document event-raising conventions - Code review checklist - Integration tests to verify events are raised --- ## 8. Success Metrics ### Implementation Success Criteria **Phase 1: Infrastructure (Day 6)** - ✅ `SaveChangesAsync` override implemented in IdentityDbContext - ✅ Domain events are being published (verified via logging) - ✅ No breaking changes to existing functionality - ✅ Unit tests pass **Phase 2: Critical Handlers (Day 6-7)** - ✅ 3-5 event handlers implemented and tested - ✅ Audit logs are being created for critical operations - ✅ Events are visible in application logs - ✅ Integration tests verify event handling **Phase 3: Full Coverage (Day 8+)** - ✅ All 15+ events have at least one handler - ✅ Audit logging complete for all major operations - ✅ Cross-module events work correctly - ✅ Performance impact is acceptable (<10ms per event) --- ## 9. Example: Complete Event Flow ### Scenario: User Role Assignment **1. Domain Event Definition** ```csharp // Domain/Aggregates/Users/Events/UserRoleAssignedEvent.cs public sealed record UserRoleAssignedEvent( Guid UserId, TenantId TenantId, TenantRole Role, Guid AssignedBy ) : DomainEvent; ``` **2. Raise Event in Aggregate** ```csharp // Domain/Aggregates/Users/User.cs public class User : AggregateRoot { public void AssignRole(TenantRole role, Guid assignedBy) { // Business logic validation if (Status == UserStatus.Deleted) throw new InvalidOperationException("Cannot assign role to deleted user"); // Raise domain event AddDomainEvent(new UserRoleAssignedEvent( Id, TenantId, role, assignedBy)); } } ``` **3. Event Handler (Audit Logging)** ```csharp // Application/EventHandlers/Users/UserRoleAssignedAuditHandler.cs public class UserRoleAssignedAuditHandler : INotificationHandler { private readonly IAuditLogRepository _auditLogRepository; public async Task Handle(UserRoleAssignedEvent notification, CancellationToken cancellationToken) { var auditLog = AuditLog.Create( entityType: "User", entityId: notification.UserId, action: $"RoleAssigned:{notification.Role}", performedBy: notification.AssignedBy, timestamp: notification.OccurredOn, tenantId: notification.TenantId); await _auditLogRepository.AddAsync(auditLog, cancellationToken); } } ``` **4. Event Handler (Cache Invalidation)** ```csharp // Application/EventHandlers/Users/UserRoleAssignedCacheHandler.cs public class UserRoleAssignedCacheHandler : INotificationHandler { private readonly IDistributedCache _cache; public async Task Handle(UserRoleAssignedEvent notification, CancellationToken cancellationToken) { // Invalidate user's permissions cache var cacheKey = $"user:permissions:{notification.UserId}"; await _cache.RemoveAsync(cacheKey, cancellationToken); } } ``` **5. Event Handler (Notification)** ```csharp // Application/EventHandlers/Users/UserRoleAssignedNotificationHandler.cs public class UserRoleAssignedNotificationHandler : INotificationHandler { private readonly INotificationService _notificationService; public async Task Handle(UserRoleAssignedEvent notification, CancellationToken cancellationToken) { // Send notification to user await _notificationService.SendAsync( userId: notification.UserId, title: "Role Updated", message: $"Your role has been changed to {notification.Role}", cancellationToken); } } ``` **6. Dispatching Flow** ``` User calls: POST /api/tenants/{tenantId}/users/{userId}/role → AssignUserRoleCommandHandler → user.AssignRole(role, currentUserId) → user.AddDomainEvent(new UserRoleAssignedEvent(...)) → _userRepository.UpdateAsync(user) → _context.SaveChangesAsync() → DispatchDomainEventsAsync() → _mediator.Publish(UserRoleAssignedEvent) → UserRoleAssignedAuditHandler.Handle() → UserRoleAssignedCacheHandler.Handle() → UserRoleAssignedNotificationHandler.Handle() → base.SaveChangesAsync() // Commit transaction ``` --- ## 10. Next Steps ### Immediate Actions (Day 6) 1. **Implement Domain Event Dispatching** - Override `SaveChangesAsync` in `IdentityDbContext` - Inject `IMediator` into DbContext - Test event dispatching with logging 2. **Create Missing Events** - `UserRoleAssignedEvent` - `UserRemovedFromTenantEvent` - `UserLoggedInEvent` 3. **Implement Critical Handlers** - `UserRoleAssignedEventHandler` (audit logging) - `TenantCreatedEventHandler` (audit logging) - `UserCreatedEventHandler` (audit logging) ### Follow-up Actions (Day 7-8) 4. **Expand Event Coverage** - Add handlers for all existing 11 domain events - Implement audit logging for all major operations - Add cache invalidation handlers where needed 5. **Testing & Validation** - Integration tests for event handling - Performance testing (event dispatching overhead) - Audit log verification 6. **Documentation** - Update architecture documentation - Document event-raising conventions - Create event handler development guide --- ## 11. Conclusion ### Summary **Current State:** - Domain event infrastructure: 80% complete - Domain events defined: 11 events (sufficient for Day 1-6) - Critical gap: Event dispatching not implemented **Recommended Action:** - Implement domain event dispatching in Day 6 (1 hour) - Add critical event handlers alongside Day 6 features (2 hours) - Complete event coverage in Day 7-8 (2-4 hours) **Total Effort:** 5-7 hours spread across Days 6-8 **Value:** - Complete audit trail for compliance - Foundation for cross-module communication - Side effects (notifications, cache invalidation) - Event sourcing ready (future) ### Decision **Proceed with Option C (Incremental Implementation)** - Phase 1 (Day 6): Infrastructure + critical handlers - Phase 2 (Day 7-8): Complete event coverage - Phase 3 (Day 9+): Advanced features (background processing, event sourcing) --- **Document Status:** ✅ Analysis Complete **Recommended Decision:** Implement domain events incrementally starting Day 6 **Next Review:** After Phase 1 implementation **Owner:** Backend Team **Last Updated:** 2025-11-03