Event-Driven Communication (Observer Pattern)

The Problem

If the Domain Layer directly calls View functions (e.g., towerView.PlayAnimation()), the Domain would need to know the View exists — violating the principle of layer separation.

The Solution: C# Events (Observer Pattern)

The Domain Layer fires events; the View Layer subscribes and reacts. The Domain doesn’t know and doesn’t need to know who is listening.

Example — Enemy fires an event when health changes:

// Enemy.cs (Domain Layer - Pure C#)
public event Action<float, float> OnHealthChanged;
public event Action KnockBack;

public void TakeDamage(float amount)
{
    CurrentHealth -= amount;
    OnHealthChanged?.Invoke(CurrentHealth, MaxHealth); // Fires event, doesn't care who listens
}

EnemyView subscribes to wire a dedicated UI Component:

// EnemyView.cs (Unity Layer)
[SerializeField] private FloatingHealthBar floatingHealthBar; // Dedicated UI component

public void Initialize(Enemy enemy)
{
    _enemy = enemy;
    
    // Wire the FloatingHealthBar's method directly to the Domain event
    _enemy.OnHealthChanged += floatingHealthBar.UpdateHealth;
    
    // Register the View's own internal handler
    _enemy.KnockBack += OnKnockBack;
}

Same pattern for Tower — fires an event when attacking:

// Tower.cs (Domain Layer)
public event Action<TowerAttackResult> OnAttack;

public void PullTrigger(List<Enemy> enemies)
{
    var result = _attackStrategy.ExecuteAttack(this, CurrentTarget, enemies);
    if (result.IsSuccess)
    {
        _currentCooldownTimer = AttackCooldown;
        OnAttack?.Invoke(result); // TowerView will receive this and spawn a projectile
    }
}

Benefits

  • Domain Layer doesn’t import Unity — can be tested entirely from the command line
  • Multiple subscribers can be added (sound, VFX, analytics) without touching Domain code
  • Easier debugging: visual bug → look in View; data bug → look in Domain