Напишите абстрактную реализацию фабрики с помощью основного DI-контейнера ASP.NET: как управлять временем жизни объекта? - PullRequest
0 голосов
/ 11 декабря 2019

Мы переносим огромную базу кода с .NET Framework на .NET core. К сожалению, часть кода, который мы переносим, ​​страдает запахами дизайна, но мы не можем свободно ломать вещи на этом этапе, и нам нужно тщательно планировать изменения во времени.

Одна из основных проблем заключается в том, что наш код опирается на большое количество абстрактных фабрик, которые, как известно, являются запахом кода (по крайней мере, при злоупотреблении). Одна связанная с этим проблема заключается в том, что контейнер Castle Windsor широко используется в существующем коде, и мы хотели бы избегать его использования внутри ядра ASP.NET, где мы предпочитаем придерживаться встроенного контейнера DI по умолчанию.

I 'Я пытаюсь понять, возможно ли переписать некоторые из наших абстрактных реализаций фабрики, в настоящее время основанные на контейнере Castle Windsor, с помощью основного DI-контейнера ASP.NET. Исходя из моего понимания внедрения зависимостей, написание таких классов в корне композиции приложения является , а не проблемой;другими словами, класс корня композиции, который зависит от контейнера DI, - это , а не способ представить указатель службы запах кода.

В настоящее время наше определение интерфейсанашей абстрактной фабрики является дырявая , потому что она предоставляет метод Release, который используется только для того, чтобы придерживаться шаблона выпуска разрешения 1011 * регистра замка Виндзора . Это текущее определение интерфейса:

public interface ICommandHandlerFactory
{
  ICommandHandler CreateHandler(Type commandType);
  void Release(ICommandHandler handler);
}

Конкретная реализация зависит от контейнера Castle Windsor, чтобы разрешить обработчики команд и освободить их, когда пользователь завершит:

public class WindsorCommandHandlerFactory
{
   private readonly IKernel _container;

   public WindsorCommandHandlerFactory(IKernel container)
   {
      _container = container;
   }

   public ICommandHandler CreateHandler(Type commandType)
   {
     // here we create the command handler type from the command type and then
     // we ask the container to resolve the command handler type
   }

   public void Release(ICommandHandler handler)
   {
     _container.ReleaseComponent(handler);
   }
}

Мой вопрос связан с управлением жизненным циклом объекта. Дело в том, что в базовом DI-контейнере ASP.NET управление временем жизни не основано на методе Release, а основано на концепции scope .

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

Это соответствующий код:

public class Worker 
{
  private readonly IServiceProvider container;

  public Worker(IServiceProvider container)
  {
    _container = container;
  }

  public void DoStuff()
  {
    using(var scope = container.CreateScope())
    {
      var service = scope.ServiceProvider.GetRequiredService<IService>();
      service.Work();
    }
  }
}

Чтобы придерживаться этого дизайна, я должен четко упростить абстрактное фабричное определение, удалив утечки (и теперь не более полезные) Release method:

public interface ICommandHandlerFactory
{
  ICommandHandler CreateHandler(Type commandType);
}

Учитывая это определение интерфейса, как я могу справиться с созданием и удалением областей контейнера?

Я не могу просто сделать следующее, потому что при удалении области действия разрешенные зависимости выводятся из эксплуатации, и поэтому вызывающий код потенциально может иметь ссылку на удаленный объект:

// this WON'T work due to the scope disposal when the service is returned
public class CommandHandlerFactory 
    {
      private readonly IServiceProvider container;

      public CommandHandlerFactory(IServiceProvider container)
      {
        _container = container;
      }

      public ICommandHandler CreateHandler(Type commandType)
      {
         using(var scope = container.CreateScope())
        {
          Type commandHandlerType = ... // build the command handler type starting from the command type

          var service = scope.ServiceProvider.GetRequiredService(commandHandlerType);
          return service;
        }
      }
    }

У вас есть идеи?

Обновление

После тщательного прочтения этой статьи я снова понял, что решениеэта проблема дизайна довольно очевидна.

Главной темой обсуждения являются внутренние проблемы с шаблоном абстрактного проектирования фабрики, поэтому решение, вероятно, избегает абстрактной фабрики вообще .

Следующий код довольно автоматически объясняет и показывает способ замены абстрактной фабрики другой абстракцией (в нашем примере это называется ICommandDispatcher), которая в основном является адаптером, используемым для вызова правильного обработчика команды для командыобрабатываться.

public interface ICommand 
  {
  }

  public interface ICommandHandler<T> where T : ICommand
  {
    void Handle(T command);
  }

  public interface ICommandDispatcher
  {
    void Dispatch(ICommand command);
  }

  public class CommandDispatcher : ICommandDispatcher
  {
    private readonly IServiceProvider _container;

    public CommandDispatcher(IServiceProvider container)
    {
      _container = container ?? throw new ArgumentNullException(nameof(container));
    }

    public void Dispatch(ICommand command)
    {
      if (command == null)
        throw new ArgumentNullException(nameof(command));

      var commandType = command.GetType();
      var commandHandlerType = typeof(ICommandHandler<>).MakeGenericType(commandType);

      using (var scope = _container.CreateScope())
      {
        var commandHandler = scope.ServiceProvider.GetRequiredService(commandHandlerType);
        ((dynamic)commandHandler).Handle((dynamic)command);
      }
    }
  }

Теперь весь код, ранее зависящий от ICommandHandlerFactory, должен зависеть от новой абстракции ICommandDispatcher. Просто не забудьте определить класс CommandDispatcher в корне композиции вашего приложения (корень композиции является единственным модулем кода, который может зависеть от контейнера IOC).

1 Ответ

2 голосов
/ 11 декабря 2019

Проще, фабрика вступает во владение для контейнера DI. Когда вы используете фабрику, фабрика управляет жизненным циклом вещей, которые она создает. Вы можете использовать DI-контейнер для управления временем жизни фабрики , но на этом все и заканчивается.

Ваш вопрос действительно слишком широкий, чтобы дать разумный ответ без переписывания кода для вас. Однако, по сути, фабрика должна реализовать IDisposable. Он должен создать новые вещи, за которые он отвечает, а затем избавиться от них в реализации метода Dispose. Со стороны Microsoft.Extensions.DependencyInjection вы зарегистрируете фабрику как одноэлементную (чтобы она могла управлять временем жизни в пределах всей области действия приложения, поскольку в этот момент она снова принимает на себя роль контейнера DI). Затем вы можете ввести фабрику туда, где вам это нужно, и использовать ее для создания того, что вам нужно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...