Orchard.CMS одна из популярных свободных open source систем управления веб контентом на базе .NET. В качестве ORM для доступа к данным используется NHibernate. Более детальную информацию можно найти на официальном сайте проекта, к тому же на Хабре уже были статьи посвященные Orchard.CMS.
Orchard CMS используется свой способ создания схемы данных посредством Migration и SchemeBuilder. Для доступа к сессии NHiberanate (ISession) и транзакциям используется специализированные интерфейсы, инкапсулирующие эти объекты внутри (ISessionHolder и ITransactionManager). Организованы собственные интерфейсы репозиториев (IRepository), реализации которых работают поверх NHibernate Linq Query.
Orchard не предусматривает прямого доступа к NHibernate по умолчанию. Ниже будут рассмотрены особенности построения и использования доменной модели на базе Orchard CMS, а также способ использования NHibernate напрямую из своего модуля.
Если бизнес уровень инкапсулирован отдельно, и Orchard.CMS обращается к сущностям по средствам веб-сервисов, проблема построения доменной модели не возникает. Это относиться к крупным проектам. Исследования в данной статье будут справедливы для проектов, в которых изначально планируется использовать общую базу и для Orchard CMS, и для сущностей бизнес логики.
Доменная модель на базе Orchard.CMS (ContentTypeDefinition)
Рассмотрим модель BlogPost в базовом модуле Блога в Orchard.CMS. (Исходный код проекта можно найти на официальном сайте). Модель типа BlogPost блога:
В модуле блога реализован widget позволяющий вывести последние N записей блога. Посмотрим на SQL запрос, который формируются для получения этого списка. Для этого воспользуемся NHProfiler и подключим модуль SoNerdy.NHProf в Orchard.CMS. (Одна из рекомендаций при разработке на Orchard.CMS – это использование NHibernate Profiler www.hibernatingrhinos.com/products/nhprof. Данная утилита незаменима в анализе и оптимизации сайта.)
Запрос, выбирающий N записей блогов, выглядит следующим образом. Join полей дополнительных контентных частей были специально удалены, чтобы сосредоточиться на базовых частях.
Краткий анализ запроса:
Результатом реализации доменной модели приложения в рамках контентных типов Orchard.CMS будет следующее:
Как вариант решения проблемы – полностью отказ от использования контентных типов при построении доменной модели. Ее реализация при помощи простых Record классов. Orchard использует AutoMapping для конфигурации NHibernate, и одна из конвенций следующая: ко всем названиям типов данных необходимо добавлять постфикс Record. Минус в том, что тестирование по-прежнему будет зависеть от контекста Orchard, и миграция в другую систему управления контентом усложниться. К тому же необходимо реализовывать отдельные модули для бизнес логики и для представления.
Использование NHibernate напрямую в Orchard.CMS
Framework Orchard.CMS предоставляет возможности конфигурировать HNibernate и использовать его возможности напрямую, без Orchard pipeline. Начиная с версии 1.7 стал доступен новый интерфейс ISessionConfigurationEvents. Пример реализации ISessionConfigurationEvents в демонстрационном проекте:
Для конфигурации своего модуля необходимо добавить реализацию этого интерфейса в свой модуль и определить конфигурацию NHibernate в методе Created. Также необходимо определить Hash модуля для автоматической перегенерации общей конфигурации NHibernate. Orchard.CMS генерирует конфигурацию NHibernate в файл mapping.bin, который находиться в папке App_Data\Sites\Default, отдельная конфигурация для каждого сайта. Для перегенерации существующей конфигурации, необходимо удалить mapping файл, и приложение создаст его автоматически.
Для доступа к сущностям возможно использовать существующие интерфейсы в Orchard.CMS:
IRepository — стандартный интерфейс, реализация, которого использует Linq To NHibernate Cacheble Query. Основные методы:
ISessionLocator – интерфейс в Orchard.CMS, который предоставляет доступ к объекту интерфейса ISession. Основные методы:
Необходимо упомянуть о транзакциях в Orchard. По умолчанию Orchard.CMS создает одну транзакцию на весь запрос и выполняет ее Commit после завершения запроса. Если запрос выполняется успешно – commit выполняется, если нет – происходит откат транзакции. Уровень изоляции данных по умолчанию – ReadCommitted. Для того чтобы завершить текущую транзакцию и открыть новую необходимо воспользоваться интерфейсом ITransactionManager. Этот интерфейс предоставляет методы для работы с транзакциями.
Удалось протестировать и реализовать демонстрационный проект с использованием Fluent NHibernate Mapping конфигурации доменной модели определенной в отдельной сборке. Проект находиться на github и доступен для скачивания.
Данная статья является рекомендацией к реализации проектов с использованием Orchard.CMS. Буду рад, если в комментариях опишут другие эффективные подходы для реализации доменной модели в рамках данной системы управления контентом.
Orchard CMS используется свой способ создания схемы данных посредством Migration и SchemeBuilder. Для доступа к сессии NHiberanate (ISession) и транзакциям используется специализированные интерфейсы, инкапсулирующие эти объекты внутри (ISessionHolder и ITransactionManager). Организованы собственные интерфейсы репозиториев (IRepository), реализации которых работают поверх NHibernate Linq Query.
Orchard не предусматривает прямого доступа к NHibernate по умолчанию. Ниже будут рассмотрены особенности построения и использования доменной модели на базе Orchard CMS, а также способ использования NHibernate напрямую из своего модуля.
Если бизнес уровень инкапсулирован отдельно, и Orchard.CMS обращается к сущностям по средствам веб-сервисов, проблема построения доменной модели не возникает. Это относиться к крупным проектам. Исследования в данной статье будут справедливы для проектов, в которых изначально планируется использовать общую базу и для Orchard CMS, и для сущностей бизнес логики.
Доменная модель на базе Orchard.CMS (ContentTypeDefinition)
Рассмотрим модель BlogPost в базовом модуле Блога в Orchard.CMS. (Исходный код проекта можно найти на официальном сайте). Модель типа BlogPost блога:
- BlogPost – тип контента (BlogPost — ContentTypeDefinition). Он состоит из следующих частей:
- BlogPostPart – контентная часть отвечающая за описание блога.
- CommonPart – стандартная контентная часть, инкапсулирует информацию об авторе и версии.
- PublishLaterPart – контентная часть для реализации черновиков.
- TitlePart – титульная часть.
- AutoroutPart – красивые URL.
- BodyPart – собственно тело записи блога.
В модуле блога реализован widget позволяющий вывести последние N записей блога. Посмотрим на SQL запрос, который формируются для получения этого списка. Для этого воспользуемся NHProfiler и подключим модуль SoNerdy.NHProf в Orchard.CMS. (Одна из рекомендаций при разработке на Orchard.CMS – это использование NHibernate Profiler www.hibernatingrhinos.com/products/nhprof. Данная утилита незаменима в анализе и оптимизации сайта.)
Запрос, выбирающий N записей блогов, выглядит следующим образом. Join полей дополнительных контентных частей были специально удалены, чтобы сосредоточиться на базовых частях.
SELECT top 12 ...
FROM v1__Orchard_Framework_ContentItemVersionRecord this_
inner join v1__Orchard_Framework_ContentItemRecord contentite1_
on this_.ContentItemRecord_id = contentite1_.Id
inner join v1__Common_CommonPartRecord commonpart3_
on contentite1_.Id = commonpart3_.Id
inner join v1__Orchard_Framework_ContentTypeRecord contenttyp2_
on contentite1_.ContentType_id = contenttyp2_.Id
WHERE contenttyp2_.Name in ('BlogPost' /* @p0 */)
and commonpart3_.Container_id = 22 /* @p1 */
and this_.Published = 1 /* @p2 */
ORDER BY commonpart3_.CreatedUtc desc
Краткий анализ запроса:
- Для получения только базовой информации контентного типа необходимо как минимум три Inner Join.
- Базовая структура всех определенных в Orchard.CMS контентных типах содержится в таблицах: ContentItemVersionRecord, ContentItemRecord и ContentTypeRecord
Результатом реализации доменной модели приложения в рамках контентных типов Orchard.CMS будет следующее:
- Все сущности будут иметь данные в одних и тех же таблицах. К примеру, для e-commerce, Id продуктов, заказов, клиентов будут храниться в одних и тех же таблицах.
- Даже небольшие запросы будут выбирать всю информацию о контентной части. К примеру, если необходимо получить название производителя для отображения под продуктом в списке. В рамках реализации Orchard будут выбраны все данные из контентной части. В противном случае теряется смысл использования техники динамического представления (Shape).
- Задача построения отчетов напрямую из базы данных очень сильно усложняется. Очень много Join.
- Миграция данных средствами базы в рамках данной реализации очень. Очень много Join.
- Снижение производительности за счет избыточного количества запросов. Очень много внимания нужно уделить на работу с Profiler, для определения узких мест.
- Orchard.CMS базируется на контентных частях и контентных типах. Текстовые разделы, блоги, html части и так далее – в большинстве случаев контентные типы или контентные части. Смешивание данные представления и доменной модели приложения – грубое нарушение инкапсуляции.
- Перенести доменную модель и бизнес логику, выполненные в контексте Orchard, на другую CMS или чистый MVC – это огромная работа.
- Тестирование, его придется выполнять в рамках Orchard контекста.
Как вариант решения проблемы – полностью отказ от использования контентных типов при построении доменной модели. Ее реализация при помощи простых Record классов. Orchard использует AutoMapping для конфигурации NHibernate, и одна из конвенций следующая: ко всем названиям типов данных необходимо добавлять постфикс Record. Минус в том, что тестирование по-прежнему будет зависеть от контекста Orchard, и миграция в другую систему управления контентом усложниться. К тому же необходимо реализовывать отдельные модули для бизнес логики и для представления.
Использование NHibernate напрямую в Orchard.CMS
Framework Orchard.CMS предоставляет возможности конфигурировать HNibernate и использовать его возможности напрямую, без Orchard pipeline. Начиная с версии 1.7 стал доступен новый интерфейс ISessionConfigurationEvents. Пример реализации ISessionConfigurationEvents в демонстрационном проекте:
public class PersistenceConfiguration : ISessionConfigurationEvents
{
public PersistenceConfiguration()
{
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public void Created(FluentConfiguration cfg, AutoPersistenceModel defaultModel)
{
cfg.Mappings(x => x.FluentMappings.AddFromAssemblyOf<Customer>());
}
...
public void ComputingHash(Hash hash)
{
hash.AddString("NHStore.Domain.Mapping");
}
}
Для конфигурации своего модуля необходимо добавить реализацию этого интерфейса в свой модуль и определить конфигурацию NHibernate в методе Created. Также необходимо определить Hash модуля для автоматической перегенерации общей конфигурации NHibernate. Orchard.CMS генерирует конфигурацию NHibernate в файл mapping.bin, который находиться в папке App_Data\Sites\Default, отдельная конфигурация для каждого сайта. Для перегенерации существующей конфигурации, необходимо удалить mapping файл, и приложение создаст его автоматически.
Для доступа к сущностям возможно использовать существующие интерфейсы в Orchard.CMS:
IRepository — стандартный интерфейс, реализация, которого использует Linq To NHibernate Cacheble Query. Основные методы:
- void Create(T entity); — Save
- void Update(T entity); — Evict/Merge
- void Delete(T entity); — Delete
- IEnumerable Fetch(); — ToReadOnlyCollection
- IQueryable Table { get; } – LinqToNHibernate Query Object
ISessionLocator – интерфейс в Orchard.CMS, который предоставляет доступ к объекту интерфейса ISession. Основные методы:
- ISession For(Type entityType); — передается тип сущности, который используется только для логирования (Logger.Debug(«Acquiring session for {0}», entityType); Объект сессии создается один на каждый запрос.
Необходимо упомянуть о транзакциях в Orchard. По умолчанию Orchard.CMS создает одну транзакцию на весь запрос и выполняет ее Commit после завершения запроса. Если запрос выполняется успешно – commit выполняется, если нет – происходит откат транзакции. Уровень изоляции данных по умолчанию – ReadCommitted. Для того чтобы завершить текущую транзакцию и открыть новую необходимо воспользоваться интерфейсом ITransactionManager. Этот интерфейс предоставляет методы для работы с транзакциями.
Удалось протестировать и реализовать демонстрационный проект с использованием Fluent NHibernate Mapping конфигурации доменной модели определенной в отдельной сборке. Проект находиться на github и доступен для скачивания.
Данная статья является рекомендацией к реализации проектов с использованием Orchard.CMS. Буду рад, если в комментариях опишут другие эффективные подходы для реализации доменной модели в рамках данной системы управления контентом.