Mediator Pattern

3 min read
Mid-level2 min read
Rapid overview

Mediator Pattern

TL;DR

Define an object that encapsulates how a set of objects interact. The mediator promotes loose coupling by keeping objects from referring to each other explicitly.

How it works

!alt text


🧩 Example β€” Mediator coordinating order validation, risk and execution

public interface ITradeMediator
{
    void Notify(object sender, string ev, Order order);
}

public class TradeMediator : ITradeMediator
{
    private readonly OrderValidator _validator;
    private readonly RiskService _risk;
    private readonly TradeExecutor _executor;

    public TradeMediator(OrderValidator validator, RiskService risk, TradeExecutor executor)
    {
        _validator = validator;
        _risk = risk;
        _executor = executor;
    }

    public void Notify(object sender, string ev, Order order)
    {
        if (ev == "PlaceOrder")
        {
            if (!_validator.Validate(order))
            {
                Console.WriteLine("Validation failed");
                return;
            }

            if (!_risk.Check(order))
            {
                Console.WriteLine("Risk check failed");
                return;
            }

            _executor.Execute(order);
        }
    }
}

public class OrderValidator
{
    private readonly ITradeMediator _mediator;
    public OrderValidator(ITradeMediator mediator) => _mediator = mediator;
    public bool Validate(Order order) => order != null && order.Amount > 0;
}

public class RiskService
{
    private readonly ITradeMediator _mediator;
    public RiskService(ITradeMediator mediator) => _mediator = mediator;
    public bool Check(Order order) => order.Amount <= 100000; // simple rule
}

public class TradeExecutor
{
    private readonly ITradeMediator _mediator;
    public TradeExecutor(ITradeMediator mediator) => _mediator = mediator;
    public void Execute(Order order) => Console.WriteLine($"Executed {order.Id} for {order.Amount}");
}

// --- Usage ---
// Compose mediator with concrete components
// var mediator = new TradeMediator(new OrderValidator(null), new RiskService(null), new TradeExecutor(null));
// Wire mediator into components and use mediator.Notify(this, "PlaceOrder", order);

βœ… Why it matters:

  • Centralizes interaction logic between components (validation, risk, execution).
  • Reduces direct dependencies between components, improving testability and maintainability.
  • Makes it easier to change coordination rules without touching each component.

Quick recall Q&A

Q: What problem does the Mediator pattern solve? A: It avoids spaghetti dependencies among collaborating components (validator, risk, executor) by centralizing communication in a mediator. Components interact via the mediator instead of referencing each other directly.
Q: How does MediatR implement the Mediator pattern in .NET? A: MediatR routes requests (commands/queries/notifications) to their handlers through a central mediator, letting senders remain unaware of receivers. Pipeline behaviors provide cross-cutting features without tight coupling.
Q: When would you build a custom mediator vs using MediatR? A: Use MediatR for request/response flows in application layers. Build custom mediators when orchestrating domain services with bespoke protocols or when you need full control over orchestration semantics.
Q: How do mediators improve testability? A: Components depend only on the mediator interface, so tests can supply stub mediators to assert interactions. You can validate coordination logic by testing the mediator in isolation.
Q: What’s a downside of mediators? A: The mediator can become a god object if it accumulates too much logic. Mitigate by splitting mediators per feature or layering policies/pipeline behaviors to keep responsibilities focused.
Q: How do you prevent mediator logic from duplicating domain rules? A: Keep mediators focused on coordination (who to notify next) and delegate business invariants to domain services/entities. If logic belongs to the domain, move it there rather than hiding it in the mediator.
Q: How do you handle asynchronous workflows with a mediator? A: Define async methods (Task NotifyAsync(...)) and await dependent operations. MediatR supports async handlers out of the box, ensuring non-blocking orchestration.
Q: What patterns complement mediators? A: Combine with CQRS (commands/queries flow through the mediator), decorators (pipeline behaviors like logging/validation), and event sourcing (mediator publishes domain events).
Q: How can mediators support extensibility? A: Register handlers or collaborators via DI so new behaviors can be added without changing existing code. For example, add a compliance handler to the notification pipeline without modifying producer services.
Q: How do you monitor mediator pipelines? A: Instrument pipeline behaviors or interceptors to log request duration, handler outcomes, and errors. This keeps observability centralized and avoids duplicating logging in every handler.

See also