Cross-Cutting Concerns (сквозная функциональность) - это аспекты системы, которые затрагивают несколько компонентов:
public interface IOrderService
{
void PlaceOrder(Order order);
}
public class OrderService : IOrderService { /* реализация */ }
public class LoggingOrderService : IOrderService
{
private readonly IOrderService _decorated;
private readonly ILogger _logger;
public LoggingOrderService(IOrderService decorated, ILogger logger)
{
_decorated = decorated;
_logger = logger;
}
public void PlaceOrder(Order order)
{
_logger.Log("Начало PlaceOrder");
try
{
_decorated.PlaceOrder(order);
_logger.Log("Успешно PlaceOrder");
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка PlaceOrder");
throw;
}
}
}
public class LogActionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
// Логирование перед выполнением
}
public override void OnActionExecuted(ActionExecutedContext context)
{
// Логирование после выполнения
}
}
[LogAction]
public IActionResult GetOrders() { /* ... */ }
public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine($"Вызов {invocation.Method.Name}");
invocation.Proceed();
Console.WriteLine($"Завершение {invocation.Method.Name}");
}
}
// Использование с Castle DynamicProxy
var generator = new ProxyGenerator();
var service = generator.CreateInterfaceProxyWithTarget<IOrderService>(
new OrderService(),
new LoggingInterceptor());
public class TransactionMiddleware
{
private readonly RequestDelegate _next;
public TransactionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, DbContext db)
{
await using var transaction = await db.Database.BeginTransactionAsync();
try
{
await _next(context);
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
}
Пример с PostSharp:
[Serializable]
public class LogAttribute : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
// Логирование входа
}
public override void OnExit(MethodExecutionArgs args)
{
// Логирование выхода
}
}
[Log]
public class OrderService { /* ... */ }
Подход | Плюсы | Минусы |
---|---|---|
Декораторы | Простота, явность | Ручное создание оберток |
Атрибуты | Простота использования | Ограниченная гибкость |
Интерцепторы | Мощность, гибкость | Сложность отладки |
Middleware | Централизованное управление | Только для HTTP конвейера |
AOP фреймворки | Максимальная автоматизация | Дополнительные зависимости |
public class ValidateAndTransactionDecorator<T> : ICommandHandler<T>
{
private readonly ICommandHandler<T> _inner;
private readonly IValidator<T> _validator;
private readonly DbContext _db;
public ValidateAndTransactionDecorator(
ICommandHandler<T> inner,
IValidator<T> validator,
DbContext db)
{
_inner = inner;
_validator = validator;
_db = db;
}
public async Task Handle(T command)
{
var result = await _validator.ValidateAsync(command);
if (!result.IsValid) throw new ValidationException(result.Errors);
await using var transaction = await _db.Database.BeginTransactionAsync();
try
{
await _inner.Handle(command);
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
}
Выбор подхода зависит от:
Лучшие практики:
Оптимальный путь:
Главное правило: "Cross-cutting код должен быть максимально предсказуемым и ненавязчивым"