📖 Business
Cross-Cutting Concerns by Design
Cross-cutting concerns — logging, authorization, caching, transaction management, validation — span multiple classes and layers but don't belong in any single domain object. Van Deursen and Seemann advocate handling these through the Decorator design pattern and DI-based interception rather than through IL weaving tools (PostSharp), runtime proxies (Castle DynamicProxy), or scattering cross-cutting logic directly into business classes. The approach treats cross-cutting concerns as composable behaviors that wrap existing abstractions, maintaining the Single Responsibility Principle while keeping the codebase free of framework-specific instrumentation. The key insight is that well-designed abstractions with DI make aspect-oriented programming possible without any AOP framework.
2
Minutes
2
Concepts
+45
XP
1
How It Works
  1. The Decorator pattern for concerns — Each cross-cutting concern is implemented as a decorator that wraps the same interface it decorates:

csharp

public class LoggingOrderService : IOrderService

{

private readonly IOrderService _inner;

private readonly ILogger _logger;

public LoggingOrderService(IOrderService inner, ILogger logger)

{

_inner = inner;

_logger = logger;

}

public void PlaceOrder(Order order)

{

_logger.Log($"Placing order {order.Id}");

_inner.PlaceOrder(order);

_logger.Log($"Order {order.Id} placed");

}

}

The business logic class has zero knowledge of logging. The Composition Root stacks the decorator.

  1. Decorator stacking — Multiple concerns compose through nesting: CachingService(LoggingService(AuthorizingService(RealService))). Each decorator handles one concern and delegates to the next. The order of stacking matters (authorization before caching, caching before logging) and is controlled explicitly in the Composition Root.
  1. Generic decorators for CQRS — When commands and queries implement generic interfaces like ICommandHandler<T>, a single generic decorator can apply a concern to all commands:

csharp

public class AuditingCommandHandler<T> : ICommandHandler<T>

{

private readonly ICommandHandler<T> _inner;

public void Handle(T command) { / audit + delegate / }

}

This eliminates the need to write per-command decorators and scales to hundreds of handlers.

  1. Why not IL weaving or runtime proxies — IL weaving (PostSharp) modifies compiled code at build time, creating invisible behavior changes that don't appear in source code. Runtime proxies (Castle) generate dynamic subclasses, which can fail with sealed classes and add debugging complexity. Both approaches couple your codebase to specific tooling. Decorators are plain code — debuggable, testable, and framework-independent.
  1. The SOLID foundation — This approach only works when abstractions follow the Interface Segregation Principle (small, focused interfaces) and the Dependency Inversion Principle (depend on abstractions). Fat interfaces make decorators painful because every method must be implemented even if the concern only applies to one.