понедельник, 5 января 2009 г.

Организация доступа к данным: как сделать шоколадно-конфетное приложение

Дано: база данных, разработчик. Разработчик разрабатывает приложение. Приложение работает с данными, хранящимися в ... базе данных - отображает, редактирует, сохраняет.

Цель: разработчик хочет сделать из приложения "конфетку" - чтобы и хорошо, и красиво, и производительно, и чтобы много пользователей, и умеренные требования к "железу".

Сразу ограничу область "приложения" - не слишком большое (не больше 2х - 3х слоев, ибо бизнес-логика присутствует), но и не слишком маленькое (т.е. о масштабировании думать уже нужно, и вариант "меня спасут помощники в Visual Studio" не пройдет). Также договоримся, что большая часть тех данных, с которыми работает приложение хранится именно в реляционной СУБД - т.е. для доступа к ним используется SQL (в той или иной форме).

Несколько лет назад (5-6) в подобных случаях советовалось не хранить SQL в коде приложения, а выносить его в хранимые процедуры. Отлично. Результатом применения такого подхода является умопомрачительное количество этих самых процедур - по одной накаждую из CRUD -операций плюс еще что-то на бизнес-логику и логику отображения данных (как раз сейчас работаю с одним legacy приложением, в котором на 130 таблиц более 550 хранмых процедур). Такое положение дел не очень хорошо по целому ряду причин:

  • сложно поддерживать
  • сложно расширять
  • сложно обновлять
  • по производительности тривиальные хранимые процедуры, реализующие CRUD-операции, хуже динамически сгенерированного SQL-кода (особенно это касается операций обновления данных)

Новое время дает нам новые инструменты. На замену хранимым процедурам пришли ORM-библиотеки. Вроде бы все стало хорошо, мило и приятно - теперь уже можно вообще не писать SQL код. Но не все так просто. Да, с CRUD-операциями ORM справляется хорошо, но вот бизнес-логика зачастую вполне ощутимо "подтормаживает", т.к. требует извлечения довольно большого количества сущностей из БД. Если мы хотим создать приложение, которое будет "быдро пошевеливаться", то полностью отказываться от хранимых процедур нам нельзя - проверку сложной бизнес-логики, работающей на множестве объектов, из которых состоит/которыми оперирует наша система, лучше держать поближе к хранилищу этих объектов, т.е. в БД.

Но и тут наша история еще не заканчивается. Если взглянуть на типичное современное (веб-)приложение, то половина (если не больше) его функциональности - это отображение различных списков. Причем чаще всего эти списки имеют похожую функциональность:

  1. необходимо обеспечить сортировку по широкому спектру атрибутов (что делает невозможным применение хранимых процедур для получения данных)
  2. отображаемые данных "собираются" из нескольких сущностей (что ставит под сомнение эффективность применения ORM)
  3. необходимо обеспечить разбивку на страницы (что вкупе с первыми двумя пунктами "напрягает" и хранимые процедуры и ORM-средства)
  4. наличие функциональности по фильтрации данных (так что либо мы изощренно издеваемся над ORM, генерирую хитрые условия, либо динамически генерируем SQL-код в хранимых процедурах)
  5. в большинстве случаев данные, отображаемые в списке, не нужно редактировать (для этого есть отдельная страница)

По моему опыту, именно такие запросы сильнее всего загружают сервер баз данных. Можно, конечно, решить все перечисленные выше проблемы "методом лома" - грубой силой заставив выбранную технологию (хранимые процедуры или ORM) делать то, что требуется. Но тогда наверняка пострадает гибкость и производительность решения. А мы хотим "конфетку". В итоге мы приходим к необходимости еще одного нишевого решения - динамической генерации SQL для отображения списков (list view). Положительный эффект на уровне БД при этом может достигать 50 - 60% (по сравнению с вариантом "в лоб").

Итак, с моей точки зрения, сбаллансированный подход к организации доступа к данных (СУБД) заключается в следующем:

  • слой бизнес-логики (aka domain model) с применением ORM
  • бизнес-логика проверяется на сервере БД и (по крайнем мере часть ее) материализуется в виде хранимых процедур
  • для отображения списков используется динамическая генерация SQL-кода, которая позволяет получить эффективный (SQL-)код и уменьшить нагрузку на сервер БД

2 комментария:

  1. Саш, ты вроде бы все правильно написал, но вот концовку я не понял. Описанные тобой основные запросы от обычного приложения - это действительно самый частый источник спагетти-кода, которого тонны либо в приложении, либо в базе данных. Динамическая генерация SQL - это, конечно, выход, но не для ORM, т.к. туда SQL встроить практически невозможно. Хотя, с другой стороны, ORM уже имеют встроенные механизмы для этой генерации, потому что запросы генерируются с учетом параметров фильтрации, пейджинга и сортировок, которые легко настраиваются при формировании зарпоса. Другой вопрос, что ORM не всегда можно использовать. В таком случае, да, можно генерировать SQL самостоятельно на лету или заняться статической кодогенерацией хранимых процедур или даже всего DAL. Благо, инструментов уже туча :)

    ОтветитьУдалить
  2. Саша (ну прямо как сам с собой разговариваю, ну чес слово ;-) ), моя мысль заключается в том, что динамической генерацией нужно заниматься не всегда, а только в некоторых случаях. Причем наличие такой генерации вовсе не отменяет использования ORM. Наверное я просто недостаточно пояснил как именно нужно (с моей точки зрения) писать SQL для list views - тогда станет более понятно, что ORM такого не может.

    ОтветитьУдалить