DI vs. AOP: внедрение логгирования

DI vs. AOP: внедрение логгирования

StepOne

Логгирование является одним из самых часто используемых примеров сквозной функциональности. Однако, каким образом внедрять этот функционал в приложение максимально просто и эффективно? Давайте разберёмся.

Шаблон проектирования "Декоратор"

"Декоратор" это структурный паттерн, который позволяет добавить объекту новое поведение без его непосредственного изменения, с помощью "упаковки" объекта внутрь специальной обёртки, реализующей тот же контракт.

Согласен, на первый взгляд, звучит достаточно заумно, разберёмся на примере.

Пусть у нас есть некий сервис, который что-то делает:

public interface IMyService
{
  void DoStuff();
}

И нам необходимо логгировать его вызов. Однако, внутри метода DoStuff расположена бизнес-логика (ключевой функционал), поэтому мы не хотим раздувать его реализацию инфраструктурой. Здесь нам может помочь этот паттерн:

public class MyServiceDecorator : IMyService
{
  private readonly ILogger _logger;
  private readonly IMyService _decoratee;
  
  public MyServiceDecorator(ILogger logger, IMyService decoratee)
  {
    _logger = logger;
    _decoratee = decoratee;
  }

  public void DoStuff()
  {
    _logger.Log("Logging DoStuff() call");
    _decoratee.DoStuff();
  }
}

Как видно из примера, мы взяли реализацию нашего сервиса, поместили её в декоратор. Затем, добавили туда логгирование через ту же инъекцию DI, затем реализовали контракт сервиса, обернув декорируемую реализацию. В итоге конечный потребитель сервиса получит реализацию, декорированную возможностью логгировать, и даже об этом не догадается.

Всё выглядит круто до тех пор, пока мы не приходим в реальный мир коммерческой разработки. Этих сервисов сотни, а может быть и тысячи. И нам нужно столько же декораторов. А потом ещё раздувать контейнер этим числом дополнительных регистраций. Выходит, что мы начинаем нарушать DRY. В этом случае рассмотрим следующую опцию.

Перехват вызовов

Как уже было сказано, логгирование является примером cross-cutting concern, сущности, послужившей одной из причин возникновения аспектно-ориентированного программирования. Если приглядеться к предыдущему примеру, то, концептуально, наши действия можно описать, как "перехват вызова" некоего сервиса.

Оказывается есть АОП инструментарий, который позволяет это сделать, реализуя специальные "перехватчики" (interceptors). Выглядит это примерно следующим образом:

public class LogInterceptor : IInterceptor
{
  private readonly ILogger _logger;

  public LogInterceptor(ILogger logger) => _logger = logger;

  public void Intercept(IInvocation invocation)
  {
    _logger.Log($"Logging invocation of {invocation.MethodName}");
    invocation.Procceed();
  }
}

Таким образом, мы создали единую логику перехвата вызовов для логгирования и зарегистрировали её в контейнере один раз. Затем достаточно просто пометить сервис, который нужно перехватить.

Такие возможности предоставляет, например, контейнер AutoFac.

© Канал StepOne

Report Page