Реализация репозиториев с использованием NHibernate и Spring.Net
Вопрос
Я пытаюсь освоить NHibernate, Fluent NHibernate и Spring.
Следуя принципам предметно-ориентированного проектирования, я пишу стандартное многоуровневое веб-приложение, состоящее из:
- уровень представления (ASP.Net)
- бизнес-уровень, включающий:
- уровень приложения (по сути, набор методов, видимых для уровня пользовательского интерфейса)
- интерфейсы репозитория и компоненты домена (используемые уровнем приложений)
- Уровень персистентности (по сути, реализация интерфейсов репозитория, определенных на бизнес-уровне).
Мне бы хотелось помочь определить способ создания экземпляра NHibernate ISession таким образом, чтобы он мог использоваться несколькими репозиториями в течение всего времени существования одного запроса к бизнес-уровне.В частности, я хотел бы:
разрешить управление экземпляром ISession и любой транзакцией вне реализации репозитория (возможно, с помощью какого-то аспекта структуры IOC, перехватчика?)
разрешить доступность экземпляра ISession для репозиториев удобным для тестирования способом (возможно, посредством внедрения или через некоторую общую абстракцию «контекста»)
избегать создания ненужных транзакций (т.когда выполнялись только операции только для чтения)
позвольте мне писать тесты, использующие SQLLite
позвольте мне использовать Fluent NHibernate
позволить реализации репозитория оставаться в неведении о среде хоста.Я пока не знаю, будет ли бизнес-уровень работать вместе с уровнем представления или будет размещаться отдельно в WCF (в IIS), поэтому я не хочу слишком тесно привязывать свой код к контексту HTTP (например, ).
Моей первой попыткой решить эту проблему было использование шаблона реестра;сохранение экземпляра ISession в свойстве ThreadStatic.Однако последующее чтение показало, что это не лучшее решение (поскольку ASP.Net может переключать поток в течение жизненного цикла страницы, я считаю).
Любые мысли, решения по деталям, названия шаблонов, указатели на актуальные образцы (NHibernate 2) будут приняты с благодарностью.
Решение
Я не использовал Spring.NET, поэтому не могу это комментировать.Однако остальное звучит замечательно (а может, и не так уж замечательно;мы едва ли первые, кто реализовал эти вещи;) аналогично моему собственному опыту.У меня тоже были проблемы с поиском одной истинно лучшей практики, поэтому я просто прочитал столько, сколько мог, и придумал свою собственную интерпретацию.
В моей ситуации я хотел, чтобы управление транзакциями/сессиями было внешним по отношению к репозиторию, а также чтобы проблемы репозитория не выходили из него (т.коду, использующему репозиторий, не нужно знать, что он использует NHibernate внутри, и ему не нужно ничего знать об управлении сеансами NHibernate).В моем случае было решено, что транзакции будут создаваться по умолчанию, чтобы разработчики не забыли о них, поэтому мне пришлось иметь механизм выхода только для чтения.Я выбрал шаблон Unit of Work с хранилищем экземпляров NHibernate ISession внутри.Код вызова (я также создал интерфейс DSL для UoW) может выглядеть примерно так:
using (var uow = UoW.Start().ReadOnly().WithHttpContext()
.InNewScope().WithScopeContext(ScopeContextProvider.For<CRMModel>())
{
// Repository access
}
На практике это может быть всего лишь UoW.Start()
в зависимости от того, сколько контекста уже доступно.А HttpContext
часть относится к месту хранения UoW, которым, что неудивительно, является HttpContext
в этом случае.Как вы упомянули, для приложения ASP .NET HttpContext
самое безопасное место для хранения вещей. ScopeContextProvider
в основном гарантирует, что для UoW предоставлен правильный контекст данных (экземпляр ISession для соответствующей базы данных/сервера, другие настройки).Концепция «ScopeContext» также упрощает вставку «тестового» контекста области.
Если пойти по этому пути, репозитории будут явно зависеть от интерфейса UoW.На самом деле, возможно, вам удастся немного абстрагировать это, но я не уверен, что вижу в этом выгоду.Я имею в виду, что каждый метод репозитория извлекает текущий экземпляр UoW, а затем извлекает объект ISession (или просто SqlConnection для тех методов, которые не используют NHibernate) для запуска запроса/операции NHibernate.Однако это работает для меня, потому что это также кажется идеальным временем, чтобы убедиться, что текущий UoW не доступен только для чтения для методов, которым может потребоваться запуск CRUD.
В целом, я думаю, что это один из подходов, который решает все ваши вопросы:
- Позволяет управлять сеансами вне репозитория.
- Контекст ISession можно имитировать или указывать на поставщика контекста для тестовой среды.
- Избегает ненужных транзакций (ну, вам придется инвертировать то, что я сделал, и иметь
.Transactional()
позвони или типа того) - Я не понимаю, почему вы не можете протестировать с помощью SQLite, поскольку это скорее проблема NHibernate.
- Я сам использую Fluent NHibernate
- Позволяет репозиторию игнорировать среду хоста (т. е. вызывающая сторона репозитория контролирует контекст хранилища UoW)
Что касается реализации UoW, я частично корю себя за то, что не осмотрелся по сторонам перед тем, как начать.Есть проект под названием машина.уоу который, как я понимаю, довольно популярен и хорошо работает с NHibernate.Я мало с ним играл, поэтому не могу сказать, решает ли он все мои требования так же четко, как тот, который я написал сам, но это также могло бы сэкономить время на разработку.
Возможно, мы получим комментарии о том, где я ошибся или как что-то улучшить, но я надеюсь, что это хотя бы в чем-то поможет.
Для справки, стек программного обеспечения, который я использую:
- АСП.NET MVC
- Свободное владение NHibernate поверх NHibernate
- Ninject для внедрения зависимостей
Другие советы
То, что вы описываете, поддерживается инфраструктурой Spring.NET практически из коробки.Только для FluentNHibernate нужно добавить собственный SessionFactory (кода не много, посмотрите здесь:Использование Fluent NHibernate в Spring.NET) в Spring.NET.
Каждый репозиторий может использовать один и тот же ISession, просто внедрите SessionFactory в свои репозитории и используйте службы транзакций Spring.NET.
Просто попробуйте, у них довольно подробная документация, имхо.