Files
ColaFlow/colaflow-api/DOMAIN-EVENTS-ANALYSIS.md
Yaojia Wang 0e503176c4 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>
2025-11-03 20:33:36 +01:00

30 KiB

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

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

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

public abstract class AggregateRoot : Entity
{
    private readonly List<DomainEvent> _domainEvents = new();

    public IReadOnlyCollection<DomainEvent> 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:

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:

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

public async Task<int> 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<AggregateRoot>()
        .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.)

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<TEvent> 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.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:

// IdentityDbContext.cs
public class IdentityDbContext : DbContext
{
    private readonly IMediator _mediator;

    public IdentityDbContext(
        DbContextOptions<IdentityDbContext> options,
        ITenantContext tenantContext,
        IMediator mediator) // ✅ Inject MediatR
        : base(options)
    {
        _tenantContext = tenantContext;
        _mediator = mediator;
    }

    public override async Task<int> 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<AggregateRoot>()
            .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:

// 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<TEvent>
  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:

// Application/EventHandlers/Users/UserCreatedEventHandler.cs
public class UserCreatedEventHandler : INotificationHandler<UserCreatedEvent>
{
    private readonly IAuditLogRepository _auditLogRepository;
    private readonly ILogger<UserCreatedEventHandler> _logger;

    public UserCreatedEventHandler(
        IAuditLogRepository auditLogRepository,
        ILogger<UserCreatedEventHandler> 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:

// Application/EventHandlers/Users/UserCreatedEmailNotificationHandler.cs
public class UserCreatedEmailNotificationHandler : INotificationHandler<UserCreatedEvent>
{
    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

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


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:

using ColaFlow.Shared.Kernel.Common;
using MediatR;

public class IdentityDbContext : DbContext
{
    private readonly ITenantContext _tenantContext;
    private readonly IMediator _mediator; // ✅ Add

    public IdentityDbContext(
        DbContextOptions<IdentityDbContext> options,
        ITenantContext tenantContext,
        IMediator mediator) // ✅ Add
        : base(options)
    {
        _tenantContext = tenantContext;
        _mediator = mediator; // ✅ Add
    }

    // ✅ Add SaveChangesAsync override
    public override async Task<int> 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<AggregateRoot>()
            .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

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

public sealed record UserRemovedFromTenantEvent(
    Guid UserId,
    TenantId TenantId,
    Guid RemovedBy
) : DomainEvent;

File: src/Modules/Identity/ColaFlow.Modules.Identity.Domain/Aggregates/Users/Events/UserLoggedInEvent.cs

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

// AssignUserRoleCommandHandler.cs
public async Task<Unit> 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

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<UserRoleAssignedEvent>
{
    private readonly ILogger<UserRoleAssignedEventHandler> _logger;

    public UserRoleAssignedEventHandler(ILogger<UserRoleAssignedEventHandler> 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:

// 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<string>(s => s.Contains("User") && s.Contains("assigned role")),
            It.IsAny<object[]>()),
        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

// Domain/Aggregates/Users/Events/UserRoleAssignedEvent.cs
public sealed record UserRoleAssignedEvent(
    Guid UserId,
    TenantId TenantId,
    TenantRole Role,
    Guid AssignedBy
) : DomainEvent;

2. Raise Event in Aggregate

// 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)

// Application/EventHandlers/Users/UserRoleAssignedAuditHandler.cs
public class UserRoleAssignedAuditHandler : INotificationHandler<UserRoleAssignedEvent>
{
    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)

// Application/EventHandlers/Users/UserRoleAssignedCacheHandler.cs
public class UserRoleAssignedCacheHandler : INotificationHandler<UserRoleAssignedEvent>
{
    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)

// Application/EventHandlers/Users/UserRoleAssignedNotificationHandler.cs
public class UserRoleAssignedNotificationHandler : INotificationHandler<UserRoleAssignedEvent>
{
    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)

  1. Expand Event Coverage

    • Add handlers for all existing 11 domain events
    • Implement audit logging for all major operations
    • Add cache invalidation handlers where needed
  2. Testing & Validation

    • Integration tests for event handling
    • Performance testing (event dispatching overhead)
    • Audit log verification
  3. 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