- •Оглавление
- •Предисловие
- •Введение
- •Часть I Обзор Глава 1 "Расслоение" системы
- •Развитие модели слоев в корпоративных программных приложениях
- •Три основных слоя
- •Где должны функционировать слои
- •Глава 2 Организация бизнес-логики
- •Выбор типового решения
- •Глава 3 Объектные модели и реляционные базы данных
- •Архитектурные решения
- •Функциональные проблемы
- •Считывание данных
- •Взаимное отображение объектов и реляционных структур
- •Отображение связей
- •Наследование
- •Реализация отображения
- •Двойное отображение
- •Использование метаданных
- •Соединение с базой данных
- •Другие проблемы
- •Дополнительные источники информации
- •Глава 4 Представление данных в Web
- •Типовые решения представлений
- •Типовые решения входных контроллеров
- •Дополнительные источники информации
- •Глава 5 Управление параллельными заданиями
- •Проблемы параллелизма
- •Контексты выполнения
- •Изолированность и устойчивость данных
- •Стратегии блокирования
- •Предотвращение возможности несогласованного чтения данных
- •Разрешение взаимоблокировок
- •Транзакции
- •Типовые решения задачи обеспечения автономного параллелизма
- •Параллельные операции и серверы приложений
- •Дополнительные источники информации
- •Глава 6 Сеансы и состояния
- •В чем преимущество отсутствия "состояния"
- •Состояние сеанса
- •Глава 7 Стратегии распределенных вычислений
- •Соблазны модели распределенных объектов
- •Интерфейсы локального и удаленного вызова
- •Когда без распределения не обойтись
- •Сужение границ распределения
- •Интерфейсы распределения
- •Глава 8 Общая картина
- •Предметная область
- •Источник данных
- •Платформы и инструменты
- •Другие модели слоев
- •Часть II Типовые решения Глава 9 Представление бизнес-логики Сценарий транзакции (Transaction Script)
- •Модель предметной области (Domain Model)
- •Модуль таблицы (Table Module)
- •Слой служб (Service Layer)
- •Глава 10 Архитектурные типовые решения источников данных Шлюз таблицы данных (Table Data Gateway)
- •Шлюз записи данных (Row Data Gateway)
- •Активная запись (Active Record)
- •Преобразователь данных (Data Mapper)
- •Глава 11 Объектно-реляционные типовые решения, предназначенные для моделирования поведения Единица работы (Unit of Work)
- •Коллекция объектов (Identity Map)
- •Загрузка по требованию (Lazy Load)
- •Глава 12 Объектно-реляционные типовые решения, предназначенные для моделирования структуры Поле идентификации (Identity Field)
- •Отображение внешних ключей (Foreign Key Mapping)
- •Отображение с помощью таблицы ассоциаций (Association Table Mapping)
- •Отображение зависимых объектов (Dependent Mapping)
- •Внедренное значение (Embedded Value)
- •Сериализованный крупный объект (Serialized lob)
- •Наследование с одной таблицей (Single Table Inheritance)
- •Наследование с таблицами для каждого класса (Class Table Inheritance)
- •Наследование с таблицами для каждого конкретного класса (Concrete Table Inheritance)
- •Преобразователи наследования (Inheritance Mappers)
- •Глава 13 Типовые решения объектно-реляционного отображения с использованием метаданных Отображение метаданных (Metadata Mapping)
- •Объект запроса (Query Object)
- •Хранилище (Repository)
- •Глава 14 Типовые решения, предназначенные для представления данных в Web Модель-представление-контроллер (Model View Controller)
- •Контроллер страниц (Page Controller)
- •Контроллер запросов (Front Controller)
- •Представление по шаблону (Template View)
- •Представление с преобразованием (Transform View)
- •Двухэтапное представление (Two Step View)
- •Контроллер приложения (Application Controller)
- •Глава 15 Типовые решения распределенной обработки данных Интерфейс удаленного доступа (Remote Facade)
- •Объект переноса данных (Data Transfer Object)
- •Глава 16 Типовые решения для обработки задач автономного параллелизма Оптимистическая автономная блокировка (Optimistic Offline Lock)
- •Пессимистическая автономная блокировка (Pessimistic Offline Lock)
- •Блокировка с низкой степенью детализации (Coarse-Grained Lock)
- •Неявная блокировка (Implicit Lock)
- •Глава 17 Типовые решения для хранения состояния сеанса Сохранение состояния сеанса на стороне клиента (Client Session State)
- •Сохранение состояния сеанса на стороне сервера (Server Session State)
- •Сохранение состояния сеанса в базе данных (Database Session State)
- •Глава 18 Базовые типовые решения Шлюз (Gateway)
- •Преобразователь (Mapper)
- •Супертип слоя (Layer Supertype)
- •Отделенный интерфейс (Separated Interface)
- •Реестр (Registry)
- •Объект-значение (Value Object)
- •Деньги (Money)
- •Частный случай (Special Case)
- •Дополнительный модуль (Plugin)
- •Фиктивная служба (Service Stub)
- •Множество записей (Record Set)
- •Список типовых решений
- •Шпаргалка
- •Как управлять сложным потоком функций приложения?
- •Как взаимодействовать с базой данных?
- •Как избежать загрузки в оперативную память всего содержимого базы данных?
- •Как сохранить структуры наследования в реляционной базе данных?
Сохранение состояния сеанса в базе данных (Database Session State)
Сохраняет состояние сеанса как обычное содержимое базы данных
Принцип действия
Когда клиент направляет серверу запрос, первое, что делает серверный объект, — обращается к базе данных и извлекает из нее данные, необходимые для удовлетворения запроса. Затем он выполняет всю необходимую работу и записывает обновленные данные обратно в базу данных.
Чтобы извлечь информацию из базы данных, серверному объекту нужны некоторые сведения о сеансе. Это можно осуществить только тогда, когда на стороне клиента будет храниться хоть какая-то информация о сеансе, как минимум его идентификатор. Впрочем, в большинстве случаев эта информация представляет собой не более чем набор ключей, необходимых для извлечения соответствующих данных.
Данные, используемые в ходе выполнения запроса, как правило, представляют собой комбинацию промежуточных данных сеанса, имеющих смысл только в контексте текущего взаимодействия с базой данных, и постоянных данных, имеющих смысл во всех взаимодействиях.
Ключевым моментом в этой схеме является то, что промежуточные данные сеанса рассматриваются как локальные по отношению к текущему сеансу и не должны применяться остальными частями системы до тех пор, пока результаты выполнения сеанса не будут зафиксированы в базе данных. Таким образом, если вы работаете с объектом заказа в контексте сеанса и хотите сохранить промежуточное состояние этого объекта в базе данных, вам нужно обрабатывать его отдельно от заказа, сохраненного в базе данных в конце этого же сеанса. В противном случае в базе данных появится масса "незаконченных" заказов, что особенно неприятно в таких ситуациях, как проверка на наличие определенных книг и подсчет ежедневной выручки.
Итак, как же отделить данные сеанса от основного содержимого базы данных? Пожалуй, самое очевидное решение — добавить к каждой строке базы данных, которая может содержать промежуточные данные сеанса, специальное поле. В качестве последнего можно использовать логическое поле с именем наподобие isPending. Однако гораздо лучше применить поле, содержащее идентификатор сеанса, что позволит легко отобрать все данные, принадлежащие конкретному сеансу. В этом случае всем запросам, извлекающим постоянные данные, понадобится отбросить промежуточные данные с помощью таких выражений, как sessioniD is not null, или воспользоваться необходимыми фильтрами.
Применять поле с идентификатором сеанса довольно неудобно, потому что всем приложениям, имеющим дело с записями базы данных, понадобится знать о присутствии этого поля, чтобы по ошибке не извлечь промежуточные данные сеанса. Иногда это неудобство можно устранить с помощью представлений и фильтров, однако применение представлений связано со своими расходами.
Возможной альтернативой является создание отдельного набора таблиц с промежуточными данными. Таким образом, если в базе данных есть таблицы для хранения заказов и пунктов заказов, понадобится добавить еще две таблицы для хранения промежуточных состояний заказов и промежуточных состояний пунктов заказов. Промежуточные данные будут сохраняться в промежуточных таблицах, а их окончательные варианты — в обычных, "настоящих" таблицах. Это позволит избежать многих неудобств, связанных с разделением данных, однако потребует реализации в слое отображения дополнительной логики выбора тех или иных таблиц, что, несомненно, усложнит структуру системы.
Зачастую постоянные данные таблиц подчиняются определенным правилам целостности, которые не распространяются на промежуточные данные. В этом случае вы можете отключить правила целостности в применении к промежуточным таблицам и активизировать их, если они все-таки понадобятся. К промежуточным данным не применяется и проверка на логическую правильность. Разумеется, на определенных этапах сеанса может понадобиться провести ту или иную проверку на правильность, однако это обычно определяется в логике серверного объекта.
Промежуточные таблицы должны представлять собой точные копии настоящих таблиц. В этом случае логика отображения будет самой простой. Используйте в промежуточных таблицах те же имена полей, что и в настоящих таблицах, а затем снабдите их дополнительным полем с идентификаторами сеанса, чтобы легко отбирать данные, принадлежащие определенному сеансу.
При сохранении промежуточных данных необходимо реализовать механизм, который будет удалять записи о брошенных или прерванных сеансах. Используя идентификатор сеанса, вы можете найти и уничтожить все ненужные данные. Если же пользователь может покинуть сеанс без уведомления сервера, понадобится организовать некоторую обработку времени ожидания. Для этого можно воспользоваться демоном, который будет запускаться каждые несколько минут и выполнять проверку наличия устаревших данных. Подобная схема обработки требует наличия в базе данных специальной таблицы, которая будет фиксировать время последнего взаимодействия с указанным сеансом.
Реализовать откат обновлений гораздо сложнее. Как выполнить откат сеанса, если в течение этого сеанса была обновлена существующая запись о заказе? Одно из возможных решений — не допускать отмену подобных сеансов. Все обновления существующих данных должны фиксироваться в таблице только в конце выполнения запроса. Эта схема очень проста и вполне соответствует видению процесса обновления самими пользователями. Все остальные альтернативы менее удобны, независимо от того, что вы используете — промежуточные поля или промежуточные таблицы. Для реализации описанной схемы при наличии промежуточных таблиц все обновляемые данные можно скопировать в промежуточные таблицы, выполнить в них необходимые изменения и перенести данные обратно в таблицы с постоянными записями по окончании текущего сеанса. При использовании промежуточных полей подобные действия возможны только тогда, когда идентификатор сеанса является частью ключа записи. В этом случае в одной и той же таблице могут одновременно находиться значения старого и нового идентификаторов, что способно крайне затруднить работу с таблицей.
Если промежуточные таблицы будут считываться только объектами, обрабатывающими сеанс, необходимость представления состояния сеанса в табличном формате крайне мала. В этом случае имеет смысл воспользоваться крупным сериализованным объектом (Serialized LOB), в результате чего сохранение состояния сеанса в базе данных превращается в сохранение состояния сеанса на стороне сервера (Server Session State).
При желании вы можете избежать всех этих трудностей, просто отказавшись от использования промежуточных данных. Другими словами, приложение можно спроектировать таким образом, чтобы все его данные рассматривались как постоянные. Разумеется, это далеко не всегда возможно и зачастую способно привести систему в состояние полной неразберихи (а потому наталкивает на мысль о необходимости явной обработки промежуточных данных). Тем не менее, если подобная возможность у вас есть, она значительно облегчит реализацию сохранения состояния сеанса в базе данных.
Назначение
Сохранение состояния сеанса в базе данных является одним из возможных вариантов обработки состояний сеанса наряду с типовыми решениями сохранение состояния сеанса на стороне сервера и сохранение состояния сеанса на стороне клиента. Каждое из них имеет свои преимущества и недостатки.
Одним из наиболее спорных аспектов сохранения состояния сеанса в базе данных является производительность. Использование серверных объектов без состояния разрешает применение пула объектов и облегчает выполнение кластеризации, что, разумеется, оказывает благоприятное влияние на производительность приложения. С другой стороны, необходимость извлечения и записи данных при выполнении каждого запроса влечет за собой определенные временные затраты. Для снижения этих затрат извлекаемые данные можно кэшировать, однако расходы на запись сократить нельзя.
Не менее важным аспектом является трудоемкость работ. Большая часть усилий программистов затрачивается на обработку состояний сеанса. Таким образом, если у вас нет состояний сеанса и промежуточные данные допускается сохранять наряду с постоянными, предпочтение следует отдать именно сохранению состояния сеанса в базе данных, поскольку оно не требует дополнительных затрат на кодирование и не приводит к падению производительности приложения (если извлекаемые объекты кэшируются).
Выбирая между сохранением состояния сеанса в базе данных и сохранением состояния сеанса на стороне сервера, обратите внимание на то, как используемый сервер приложений поддерживает кластеризацию и отказоустойчивость. В большинстве стандартных решений реализовать кластеризацию и устойчивость к отказам легче, если состояние сеанса хранится в базе данных.
