Пишем код

Заметки о .net разработке

Session/context management в настольных приложениях

without comments

Когда в сети говорят об ORM в .net — очень часто в качестве сферы применения «по умолчанию» рассматривается ASP.Net (и MVC в частности).
Происходит это, как мне кажется, потому, что именно веб — среда наиболее динамичная, открытая и быстро подхватывающая новые веяния, вследствие полной свободы в технических решениях. В случае, к примеру, Win Forms приложений переход со второго фреймворка на 4-ый — сложнейший шаг (апдейт должен затронуть всех пользователей, а у некоторых, может быть, еще win2000 :)), а для ASP.Net разработчиков проапгрейдить сервер, обычно, не проблема.
Еще одна причина может быть в том, что «принципы работы» в случае веба типичны, и рабочий цикл мал — пришел запрос, сгенерили ответ, отдали клиенту, вернулись в начальное состояние. Следовательно и подходы, применяемые одной компанией, легко адаптируются и в другой.
Desktop-приложения же часто очень велики, с длинным циклом разработки и, обычно, с устоявшимися внутри компании методиками работы, которые не всегда легко переносимы.

В этом посте я хотел бы рассказать об одной из специфических трудностей, с которыми можно столкнуться при работе с ORM в настольных приложениях, а именно: управление контекстом/сессией.

Корень проблемы прост: если в web наиболее простой и одновременно удобной практикой считается использование session-per-request, когда все запросы к БД в рамках ответа на пользовательский запрос, идут в общей сессии. Это даёт уверенность, что все объекты, с которыми идет работа получены именно из этой сессии и ситуация, в которой у вас есть два доменных объекта из двух разных сессий фактически невозможна.

В настольных приложениях аналогичного «request» просто не существует. Распространенной практикой является session-per-presenter(/viewmodel), но и этот принцип может столкнуться с проблемой в реализации.
Допустим, к примеру, мы используем репозитории:

public class SomeViewModel {
  //как обеспечить общую сессию в двух репозиториях? - приходится SomeViewModel создавать через фабрику.
  public SomeViewModel (IRepository<User> userRepo, IRepository<Order> orderRepo) {}

  //можно использовать фабрику репозиториев, которая будет во все созданные репозитории подсовывать одну и ту же сессию.
  public SomeViewModel (IRepositoryFactory repoFactory) {
    _userRepo = repoFactory.Resolve<User>();
    _orderRepo = repoFactory.Resolve<Order>();
  }

  //а можно использовать юнитОфВорк из <a href="http://www.arturdr.ru/net/sloy-dostupa-k-dannyim-v-net-prilozheniyah/">предыдущего поста</a>, тогда заморачиваться с фабриками не придется
  public SomeViewModel (IUnitOfWork unitOfWork) {
  }
}

С репозиториями разобрались, чуть сложнее с сервисами, если мы используем анемичную доменную модель.

public class UserService {
  public void ProcessOrder(User user, Order order) {
  }
}

Проблема: далеко не факт, что пришедший нам в эту функцию user и order запрошены из одного и того же контекста. Проблема вторая — непонятно, должен ли этот сервис самостоятельно сохранить данные в БД (сделать commit у сессии) или нет.
Неплохим решением в данном случае видится активное использование DDD, при котором число таких сервисов должно сократиться до минимума. Оставшиеся сервисы будут попадать в одну из следующих категорий:

  1. Сервисы, проводящие некоторые длительные\ресурсоемкие операции. Такие сервисы, очевидно, должны работать с собственными Сессиями и самостоятельно их коммитить. Для окончательного разрушения двусмысленности такие сервисы могут принимать не сущности User или Order, а их идентификаторы (User.Id и Order.Id).
  2. Сервисные операции, завязанные на объемную дополнительную выборку из БД, которые не получилось сделать частью доменной модели. Такие сервисы должны наряду с прочими аргументами принимать UnitOfWork, из которого они и будут осуществлять это дополнительную выборку. Таким образом все запрошенные сущности будут гарантированно получены из одной сессии.

Еще одно проявление той же проблемы — обмен данными между различными вьюшками приложения. Очевидно, что сессия у каждой вьюшки своя, и при взаимодействии вьюшек весьма вероятно перемешивание сущностей из разных сессий в рамках одной вьюшки.
Собственно, решается эта проблема аналогично проблемам с сервисами — вьюшки между собой должны обмениваться не сущностями, а их идентификаторами (эмулируя data transfer object — DTO), либо, если вьюшки очень тесно связаны, например, одна является частью другой, они должны работать с одной сессией (инъектированной в конструкторе).

Опубликовать в Facebook
Опубликовать в Google Plus

Written by Shaddix

Сентябрь 9th, 2011 at 7:06 пп

Posted in .net,EF,nHibernate,WPF

Leave a Reply