Однако запросы типа «выбрать сотрудников, зарплата которых в течение последнего года не превышала среднюю за предыдущий год» уже вызывают проблемы на уровне встроенного языка. Тогда разработчики идут единственно возможным путём: выбираем коллекцию объектов и в циклах фильтруем и обсчитываем, вызывая методы связанных объектов. Или используем тот же LINQ над выбранным массивом. Количество промежуточных коротких SQL-запросов к СУБД при такой обработке коллекций может исчисляться десятками тысяч.

<p>Триггер как идеальная концепция для NHibernate</p>

Обычно разработчикам баз данных я рекомендую избегать необоснованного использования триггеров. Потому что их сложнее программировать и отлаживать. Оставаясь скрытыми в потоке управления, они напрямую влияют на производительность и могут давать неожиданные побочные эффекты. Пользуйтесь декларативной ссылочной целостностью (DRI[58]) и хранимыми процедурами, пока возможно. А если ваш администратор баз данных склонен к параноидальным практикам «запрещено всё, что не разрешено», избегайте программировать на уровне СУБД, исключая критичные по производительности участки. Приходится говорить это с сожалением…

Однако стоит посмотреть на слой домена, живущего под управлением NHibernate, как становится ясно, что триггер в СУБД – это достаточно простая и хорошо документированная технология. В то время как NHibernate предлагает прикладному разработчику целый зоопарк триггероподобных решений.

Во-первых, имеется древний способ реализации классом домена интерфейса из пространства имён NHibernate.Classic. Например, IValidate. Вроде бы удобно: реализовал и делай проверки, генерируя исключения для отмены транзакции. Но вот незадача: при удалении объекта этот метод не срабатывает, нужно использовать другие подходы.

Во-вторых, после осознания авторами недостаточности IValidate и ILifeCycle была введена система прерываний (interceptors). Это уже больше, чем бесплатный хлеб на завтрак. Однако в обработчиках типа Save или FlushDirty в качестве аргументов используются массивы состояний объекта. То есть изменять сам объект в них напрямую нельзя: в общем случае это просто не срабатывает, но могут быть и побочные эффекты. Нужно, ни много ни мало, поискать индекс элемента в массиве имён свойств объекта, затем по найденному номеру изменить значение в другом массиве текущего состояния объекта. Что-то вроде такого кода:

Изменение свойств объекта в обработчике NHibernate

int index = Array.IndexOf(propertyNames, "PhoneNumber");

if (index!= -1)

state[index] = "(123)456789";

Создавать новые, извлекать, изменять или удалять существующие объекты с последующим сохранением внутри обработчика совершенно не рекомендуется. Кроме собственно гонок (race condition), когда обработчик одного класса создаёт другой, а тот что-то делает с первым, могут быть и другие эффекты, включая бесконечную рекурсию. Шаг вправо, шаг влево – стреляют боевыми и без предупреждения. Неплохая иллюстрация к теме декларируемой безопасности языка C# или Java. Рекомендуемая практика обхода ловушек такого рода – запрограммировать собственную защищённую (thread safe) очередь, куда складывать все созданные или изменённые объекты, а в событиях BeforeCommit или AfterCommit эту очередь обрабатывать.

В-третьих, механизм прерываний также признан несовершенным, после чего был введён механизм событий (events), коим, начиная со второй версии, всем следует пользоваться.

Дело в том, что в сессии вы можете зарегистрировать только один класс, реализующий обработчики прерываний. И если у вас достаточно много разной обработки, то получается так называемый «волшебный класс», который реализует всё. Это неудобно, даже если использовать класс в качестве пустого фасада.

Теперь же вы можете зарегистрировать неограниченное количество классов-слушателей (listeners), ожидающих то или иное событие и соответствующим образом реагирующих на него. Один класс может реализовывать несколько обработчиков событий, будучи зарегистрированным для прослушивания нескольких их типов.

С точки зрения архитектуры это несомненный плюс, реализацию можно разбить на независимые классы. Однако для снижения побочных эффектов, прежде всего рекурсии и гонок, не сделано ничего. В том же SQL Server, напомню, рекурсия в триггерах отключена по умолчанию, поскольку трудно сходу придумать случай, когда она нужна. А в событиях NHibernate каждый сам себе вредитель. При этом однозначной методики и документации нет, нескольким десяткам типов событий в официальной документации отведено меньше страницы. Существует огромное количество записей в блогах, тиражирующих одни и те же конкретные примеры – аудит, прежде всего. Но однозначной выверенной практики нет, в каждом конкретном случае надо проводить тесты. Например, для манипуляции объектами рекомендуется создавать дочернюю сессию, что также не всегда избавляет от побочных эффектов.

Перейти на страницу:

Все книги серии Библиотека программиста

Похожие книги