Дмитpий Hecтepук

Блог о программировании — C#, F#, C++, архитектура, и многое другое

Пример использования IoC для репозитариев

20 комментариев

В связи с оживлением темы DI/IoC с использованием Unity на Хабре, я решил выложить небольшой пример практического использования этого фреймворка.

Сценарий:

Ваше приложение реализует persistence с помощью паттерна репозитарий. Репозитарий используется в приложении Asp.Net MVC и позволяет контроллерам работать с persistence layer. Для тестирования, репозитарий должен быть in-memory, а в продашкн он должен работать с живой базой.

Вот пример того, как овладеть ситуацией с использованием Unity. Для начала, посмотрим на интерфейс репозитария. Для наглядности я оставлю всего один метод:

interface IRepository<T>
{
  void Create(T obj);
}

На практике у нас будет 2 репозитария – FakeRepository и DbRepository, которые будут специализированы для разных типов сущностей. Например, если у нас есть класс Person

class Person
{
  public string Name { get; set; }
  public int Age { get; set; }
}

то для него будут созданы следующие классы-репозитарии:

class FakePersonRepository : IRepository<Person>
{
  private readonly IList<Person> people = new List<Person>();
  public void Create(Person person)
  {
    people.Add(person);
  }
}
 
class DbPersonRepository : IRepository<Person>
{
  public void Create(Person person)
  {
    using (var ctx = new DataContext())
    {
      ctx.AddPerson(person);
      ctx.SaveChanges();
    }
  }
}

Теперь насчет использования этих репозитариев. Есть море вариантов, но я остановлюсь на самом простом – а именно на варианте где доступ к определенному сервису запрашивает сам контроллер. Чтобы “централизовать” выдачу сервисов, мы используем один базовый класс для всех контроллеров. Этот базовый класс как раз умеет “выдавать” определенные репозитарии по запросу:

class BaseController : Controller
{
  public IUnityContainer RootContainer { get; set; }
  private IUnityContainer fakeContainer, dbContainer;
  public BaseController()
  {
    RootContainer = new UnityContainer();
    dbContainer = RootContainer.CreateChildContainer()
      .RegisterType<IRepository<Person>, DbPersonRepository>();
    fakeContainer = RootContainer.CreateChildContainer()
      .RegisterType<IRepository<Person>, FakePersonRepository>();
  }
}

Субконтейнеры – это удобная фича Unity которая позволяет “наследовать” настройки родительского контейнера. Это позволяет нам определять как “общие” сервисы (например логирование) так и “частные”, в том числе тот же репозитарий.

Теперь дело осталось за малым – написать небольшой шлюз для переключения. В нашем случае он будет выглядеть вот так:

protected T Get<T>()
{
  #if DEBUG
  return fakeContainer.Resolve<T>();
  #else
  return dbContainer.Resolve<T>();
  #endif
}

Ну а конкретный доступ к тем же репозитариям осуществляется, например, вот так:

protected IRepository<Person> GetPersonRepository()
{
  return Get<IRepository<Person>>();
}

Естественно, пример выше полезен для весьма конкретной задачи, и на практике каждый use case разный, со своими тонкостями. Чего мы достигли? Примерно следующего:

  • При запуске программы будет использоваться тот контейнер, и тем самым те сервисы, которые уместны

  • Для новой конфигурации (например staging) нужно просто добавить еще один субконтейнер

  • Для тестирования можно переконфигурировать “корневой” контейнер

  • Если надо, можно вынести весь функционал из BaseContainer-а куда-то еще, сделав например singleton factory

Этот пример может и не является примером “идеального” использования Unity, но по крайней мере он успешно используется в реальном проекте. Конструктивная критика welcome :)

Advertisements

Written by Dmitri

26 апреля 2010 в 16:30

Опубликовано в Technology, unity

комментариев 20

Subscribe to comments with RSS.

  1. ну вот опять…
    сколько не читаю про IoC не могу понять одного, а именно зачем оно нужно (ну вернее я в конечном итоге разобрался). Все примеры притянуты за уши, как и этот.

    хотелось бы увидеть не идеальный, а рабочий пример с использованием Ioc, причем такой пример, где использование IoC было правильным архитектурным решением.

    я заранее знаю, что такой пример мне никто не приведет.

    vadim

    26 апреля 2010 at 17:58

    • Это не притянуто за уши, это реальный пример из реально работающего проекта. Это рабочий пример в котором использование IoC как раз является правильным архитектурным решением, хотя естественно имеются варианты.

      Dmitri

      26 апреля 2010 at 18:26

      • Вот… «имеются варианты», ваш ответ именно такой как я и ожидал.
        Спасибо.
        Просто очень многие считают IoC панацеей, гениальнейшим изобретением, ну и т.д.

        vadim

        26 апреля 2010 at 20:32

    • А попробуйте прочитать мой пост на эту тему:
      http://xoposhiy.livejournal.com/93088.html

      Я недавно делал доклад на Уральской Юзер-группе, на которой очень обстоятельно рассказывал про то, зачем они вообще нужны. Презентация без моего голоса не очень самодостаточна, но все равно можно глянуть тут: http://docs.google.com/uc?id=0B1W9g0IejZAVYzM4YzY5MDQtN2JmZC00MDk5LTg4MzktZTE5MTEzNzBkNzll&export=download&hl=ru

      xoposhiy

      27 апреля 2010 at 6:36

      • OK, прочитал. Но я в упор не понимаю причем тут convention over configuration. Не дадите ссылочку на какой-нибудь иллюстрирующий это пример?

        Dmitri

        27 апреля 2010 at 18:21

    • Приятнут за уши потому что в объёмах статьи сложные зависимости никто разбирать не будет.
      Живой пример:
      Global.asax.cs:

      private IUnityContainer InitializeApplicationContainer()
      {
       //регистрируем DbPersonRepository или FakePersonRepository в зависимости от енвайрмента
      }
      protected void Application_Start() {
       IUnityContainer ctx = InitializeApplicationContainer();
       var controllerFactory = new InjectionEnabledControllerFactory(ctx);
      ControllerBuilder.Current.SetControllerFactory(controllerFactory);
       ...
      }
      

      InjectionEnabledControllerFactory.cs:

      public class InjectionEnabledControllerFactory : DefaultControllerFactory 
      {
        readonly IUnityContainer m_container;
              
      public InjectionEnabledControllerFactory(IServiceContext container) 
       {
        m_container = container;
       }
       protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) 
      {
      IController controller = null;
      if (null != controllerType &amp;&amp; null != (controller = base.GetControllerInstance(requestContext, controllerType)))
      {
       //интефрейс маркер, значит в этом контроллере мы хотим обращаться к данным
       if (controller is IDataController)
       {
        m_container.BuildUp(controller as IDataController);
       }
       return (controller);
      }
      }
      

      HomeController.cs:

      public class HomeController : Controller, IDataController
      {
       //в зависимости от инициализации контейнера тут будет DbPersonRepository или FakePersonRepository 
       [Dependency]
       public IRepository PersonRepository { get; set; }
      }
      

      Unity – это способ протащить всё что нужно туда где это используется не плодя немеренно сингелтонов и статических свойств. Добавить сюда логер: 2 Строчки кода, сменить фейковую реализацию на живую – поправить одну строчку в 1 месте.

      Alexey

      27 апреля 2010 at 10:04

      • поправка: public InjectionEnabledControllerFactory(IServiceContext container) должно быть public InjectionEnabledControllerFactory(IUnityContainer container) просто в реальном приложении контейнер конечно обёрнут :)

        Alexey

        27 апреля 2010 at 10:07

  2. Не надо создавать BaseController. Инициализации зависимостей и их инжекции в ASP.NET MVC специально создали IControllerFactory.

    Александр Бындю

    26 апреля 2010 at 18:12

  3. Однако FakePersonRepository и DbPersonRepository не идентичны в своем поведении.
    Например:
    r1 = Resolve<IRepository>();
    r2 = Resolve<IRepository>();

    Person p1 = new Person(id);
    r1.Add(p1);

    Person p2 = r2.Get(id);

    В тестовом FakePersonRepository p2 будет null, а в DbPersonRepository — p1 == p2.

    Подобные ситуации важно учитывать при написании fake-реализаций.

    ankstoo

    26 апреля 2010 at 18:24

  4. @vadim

    Можете прочитать в блоге у Александра Бындю, он там расписал все подробно. Описанное там решение аналогично применяемому в наших рабочих проектах.

    hazzik

    26 апреля 2010 at 18:24

  5. Вы своим #if-#else-#endif внесли в код продукта тестовый код, что делать ни в коем случае не рекомендуется за исключением, когда протестировать код без такого внесения просто невозможно, например в процедурных языках программирования, таких как pascal и c.

    В приложении должно быть только 1 Resolve, который делается в точке входа, например в функции Main для настольных приложений.

    PS: удалять комментарии просто неприлично

    hazzik

    26 апреля 2010 at 21:42

    • #if-#then-#else мне приемлим, я им доволен. «ни в коем случае» это не для меня.

      В приложении может быть ровно столько Resolve() сколько надо. Никаких правил нет.

      PS: блог мой, модератор тут я.

      Dmitri

      27 апреля 2010 at 8:28

      • Я понимаю, коммент hazzik уж очень категорично выглядит и несколько раздражает своей религиозностью.

        Но по сути он прав, у вас в примере показан ряд не очень приятных практик. #if — #else — это отстой и на то есть целая куча. Если вы их не осознаете, то лучше бы вам их осознать. Если осознаете, то лучше бы вам такие примеры другим не показывать.

        Минимизация количества Resolve — это тоже признанная практика. Без нее контейнер постепенно превращается в сервис-локатор, со всеми его недостатками.

        Зачем было использовать дочерние контейнеры тоже непонятно. Тот же #if можно было воткнуть непостредственно в конструктор BaseController-а.

        xoposhiy

        27 апреля 2010 at 11:06

    • Полностью согласен.

      Сама по себе строчка кода
      return fakeContainer.Resolve();
      ни в коем случае не должна быть в production-коде.

      VasilioRuzanni

      23 июля 2010 at 20:19

  6. Нечего себе, спасибо!

    basarabcik

    26 апреля 2010 at 22:47

  7. Статья хорошая для старта.
    Пример IoC для репозитория — это самый классический и ,как не удивительно, избитый пример. Честно, хотелось бы увидеть применение IoC и в других случаях.

    Алексей

    27 апреля 2010 at 17:07

  8. Мне как и многим кажется, что это не совсем корректное применение DI. Все-таки лучше когда контроллер получает зависимости извне (собственно с этом DI и заключается) а не сам тянет их откуда-то.

    Но меня больше забавляет другое. Замечена такая тенденция — если в статье встречаются термины IOC или DI, то в комментариях обязательно вылезит чувак, который скажет (в той или иной форме), что IoC — это отстой и что он до сих пор не понимает за чем оно все нужно))

    admax

    7 мая 2010 at 15:20


Оставить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: