Введение в превалирование объектов
Используйте всю мощь объектно-ориентированного подхода, благодаря лучшей стратегии хранения
Хранение состояний и данных всегда было проблемой объектно-ориентированного программного обеспечения. Годами разработчики хранили объектные данные, используя для этого множество различных способов, включая реляционные базы данных, flat файлы и XML. Ни один из этих подходов не мог обеспечить полную объектную ориентированность программ. Команда разработчиков Prevayler своей концепцией превалирования объектов изменила текущее положение вещей.
Примечание: Возможно, вам потребуется знакомство с концепцией командных объектов. Обсуждаемая тема тесно перекликается с литературой по шаблонам разработки.
Чем плох метод хранения, который вы используете
Сегодня хранение данных для объектно-ориентированных систем - невероятно громоздкая задача, если занимаешься разработкой приложений различного вида. Разработчик должен преобразовать объекты в таблицы баз данных, XML файлы, или использовать какой-нибудь другой не объектно-ориентированный способ представления данных, полностью нарушающий инкапсуляцию. Единственное решение этой проблемы заключается в превалировании объектов.
Метод Превалирования
Превалирование объектов - это концепция, разработанная Клаусом Вьюстэфилдом (Klaus Wuestefeld) совместно с коллегами из Objective Solutions. Ее первая реализация, известная как Prevayler, вышла в ноябре 2001 в качестве проекта с открытым исходным кодом (См. Ссылки). Сегодня уже существует Prevayler версии 1.3.0, содержащий около 350 строк кода. Вы можете счесть объем кода слишком незначительным, чтобы оказаться чем-нибудь полезным, но я, опираясь на личный опыт работы над многими проектами, могу подтвердить, что Prevayler в несколько раз быстрее ряда ведущих реляционных баз данных с открытым исходным кодом. Все дело в простоте.
Концепция и реализация превалирования объектов проста и может быть воплощена на любом объектно-ориентированном языке программирования, который позволяет сериализовать объекты (возможность, предоставляемая многими современными объектно-ориентированными языками).
В превалирующих системах все сохраняется в RAM, как если бы вы просто использовали язык программирования. Сериализуемые командные объекты управляют всеми необходимыми изменениями, требующимися для хранения. Запросы запускаются из написанных на языке Java объектов, предоставляя разработчику всю гибкость Collections API и других API, таких как Jakarta Commons Collections и Jutil.org. (См. Ссылки)
Рис. 1. Как работает превалирующая система
Прежде чем изменения коснутся бизнес объектов, каждая команда сериализуется и записывается в файл отчета (см. Рис.1). Затем, каждая команда незамедлительно выполняется. При необходимости, в период простоя, система может сделать снимок бизнес объектов, объединив все команды в один большой файл для сокращения времени на перезагрузку.
При восстановлении системы после сброса или сбоя, система восстанавливает сохраненные состояния из файла снимка (если он доступен), а затем считывает команды из файлов отчетов, созданных с момента создания последнего снимка. Эти команды применяются к бизнес объектам, как если бы они поступали от клиентов системы, включая системные часы. Таким образом система возвращается в состояние, в котором пребывала до сбоя.
Чтобы превалирующая система работала правильно, ее бизнес объекты должны удовлетворять двум простым правилам. Бизнес объекты должны быть:
- Сериализуемыми - в любой момент времени системе может потребоваться сохранить объект на диске, или другом носителе
- Детерминированными - получив входящие данные, методы бизнес объектов должны всегда возвращать один и тот же вывод
Это особенно важно, когда бизнес объекты взаимодействуют с
системными часами. Гуру объектно-ориентированного подхода часто говорят, что
системные часы - внешнее действующее лицо по отношению к системе. Prevayler
помогает вам придерживаться этой точки зрения, обеспечивая класс адаптера,
называемый AlarmClock
, который выполняет для разработчика
приложения слежку за тактами системных часов.
Создание вашей первой Превалирующей системы
Теперь, чтобы приступить к созданию первой превалирующей системы нам нужно скачать Prevayler с web сайта проекта. Убедитесь в правильности установки, проверив его наличие в вашем CLASSPATH.
Начало
Создаваемая превалирующая система будет простейшим менеджером
подключений, работающим из командной строки. Вам потребуется найти идентификатор
пользователя, пароль, имя и адрес электронной почты. Начнем с описания класса
User
(см. Листинг
1):
Листинг 1. Описание класса Use
package org.prevayler.intro.users;
import java.io.Serializable;
/** Представляет пользователя системы.
* @author Carlos Villela
*/
public class User implements Serializable {
private String name = "";
private String email = "";
private String login = "";
private String password = "";
// далее идут методы get и set
// ...
}
Этот простой, содержащий лишь свойства класс - единственный бизнес объект нашей простой демонстрации возможностей превалирования и Prevayler. В реально существующих приложениях вы можете указать большее число бизнес объектов, в соответствии с вашей задачей.
Теперь включите HashMap
в PrevalentSystem
, как
показано на Листинге 2.
PrevalentSystem
package org.prevayler.intro.users;
import java.util.HashMap;
import org.prevayler.implementation.AbstractPrevalentSystem;
/** Превалирующая система для Демонстрационного менеджера пользователей.
* @author Carlos Villela
*/
public class UserLogonSystem extends AbstractPrevalentSystem {
/**
* Карта, содержащая ссылки пользователей системы.
*/
private HashMap users;
public UserLogonSystem() {
this.users = new HashMap();
}
public HashMap getUsers() {
return users;
}
public void setUsers(HashMap users) {
this.users = users;
}
}
Теперь вы описали свою систему, используя класс
AbstractPrevalentSystem
. Вы можете реализовать интерфейс
PrevalentSystem
непосредственно, если вам необходимо использовать
системные часы, или если вашей системе нужно расширить какие-нибудь другие
классы.
Создание команд
Прежде чем вы сможет что-либо сделать с этой системой, вам нужно
модифицировать и запросить ее данные. Начните с команды AddUser
,
как показано в Листинге 3:
package org.prevayler.intro.users.commands;
import java.io.Serializable;
import java.util.HashMap;
import org.prevayler.Command;
import org.prevayler.PrevalentSystem;
import org.prevayler.intro.users.User;
import org.prevayler.intro.users.UserManager;
/** Добавляет пользователя в UserManager
* @author Carlos Villela
*/
public final class AddUser implements Command {
private User user;
/**
* @see Command#execute(PrevalentSystem)
*/
public Serializable execute(PrevalentSystem system) throws
InvalidUserException {
if (user == null)
throw new InvalidUserException("null user");
if (user.getLogin() == null)
throw new InvalidUserException("null login");
if (user.getPassword() == null)
throw new InvalidUserException("null password");
if (user.getLogin().length() < 1)
throw new InvalidUserException("invalid login size");
if (user.getPassword().length() < 6)
throw new InvalidUserException("invalid password size");
UserManager users = (UserManager) system;
HashMap usersMap = users.getUsers();
assert usersMap != null;
if (usersMap.containsKey(user.getLogin()))
throw new InvalidUserException("login already exists");
usersMap.put(user.getLogin(), user);
return this;
}
// методы get и set для пользователей...
}
Давайте поподробнее рассмотрим этот код. Команда
AddUser
аналогична SQL команде INSERT
- используя
метод HashMap.put
выполняет те же операции, что и при добавлении
данных в индексированную реляционную таблицу. Вы можете создавать более сложные
команды, наподобие той, которая рассмотрена далее.
На листинге 4 представлен исходный код более сложной команды,
ChangeUser
:
ChangeUser
package org.prevayler.intro.users.commands;
import java.io.Serializable;
import java.util.HashMap;
import org.prevayler.Command;
import org.prevayler.PrevalentSystem;
import org.prevayler.intro.users.User;
import org.prevayler.intro.users.UserManager;
public final class ChangeUser implements Command {
private User user;
public Serializable execute(PrevalentSystem system) throws
InvalidUserException {
if (user == null)
throw new InvalidUserException("null user");
if (user.getLogin() == null)
throw new InvalidUserException("null login");
if (user.getPassword() == null)
throw new InvalidUserException("null password");
if (user.getLogin().length() < 1)
throw new InvalidUserException("invalid login size");
if (user.getPassword().length() < 6)
throw new InvalidUserException("invalid password size");
UserManager users = (UserManager) system;
HashMap usersMap = users.getUsers();
if (!usersMap.containsKey(user.getLogin()))
throw new InvalidUserException("non-existent login");
usersMap.remove(user.getLogin());
usersMap.put(user.getLogin(), user);
return this;
}
// методы get и set для пользователей...
}
В команда ChangeUser
иллюстрирует одну тонкость: когда
я хочу изменить User
, я сначала проверяю его наличие в системе. На
практике для этого используются команды SELECT
и
UPDATE
. Поскольку объектно-ориентированные программисты не должны
тратить время на размышления о возврате к предыдущей транзакции, я проверяю все
моменты, которые могут служить источником ошибок. Я могу это сделать, поскольку
мне доступны все данные и перед каждым изменением выполняются необходимые
проверки. Транзакция теперь является методом - ее выполнение может либо успешно
завершиться, либо явиться причиной исключительной ситуации
Exception
.
Впрочем, вы можете реализовать программу, при выполнении которой возвратов не происходит, но мы не будем рассматривать примеров таких реализаций.
По этим двум командам вы можете решить, что вы используете SQL. Вы
не ограничены в средствах лишь этим. Команды могут быть намного умнее. Вы можете
рассматривать их как агенты вашей системы. Они могут начинать работу даже прежде
чем вы запустите их, используя execute()
, проверять данные и
состояния, выдавать предупреждения вашему приложению, и многое другое. Ваши
операции с данными свободны от ограничений SQL и других языков запросов.
Собираем все воедино
Затем я создал класс Main
, обеспечивающий интерфейс
командной строки. Я мог создать любой тип взаимодействия с пользователем,
включая апплет, графический интерфейс Swing, сервлет, или другой, который может
быть использован для создания новых команд и их применения в системе.
На листинге 5 представлен код, создающий интерфейс командной строки:
Листинг 5. Создание класса Main
/**
* Главный класс для демонстрации Менеджера пользователей.
*/
public class Main {
public static void usage() {
System.out.println(
"Usage: Main <list|add|change|del> login <parameters>\n\n"
+ "Parameters:\n"
+ " list: none\n"
+ " add: <login> <password> <name> <email>\n"
+ " change: <login> <password> <name> <email>\n"
+ " del: <login>\n");
System.exit(0);
}
public static void main(String[] args) {
try {
SnapshotPrevayler prevayler =
new SnapshotPrevayler(new UserManager());
if (args.length < 1) {
usage();
} else if (args[0].equalsIgnoreCase("list")) {
listUsers(prevayler);
} else if (args[0].equalsIgnoreCase("add") & args.length >= 5) {
addUser(prevayler, args[1], args[2], args[3], args[4]);
} else if (args[0].equalsIgnoreCase("del") & args.length >= 2) {
deleteUser(prevayler, args[1]);
} else if (args[0].equalsIgnoreCase("change")) {
changeUser(prevayler, args[1], args[2], args[3], args[4]);
} else {
usage();
}
} catch (Exception e) {
e.printStackTrace();
}
}
// клиентские методы...
}
Листинг 6 и Листинге 7 демонстрируют методы addUser
и changeUser
соответственно. Для краткости я сократил листинги для deleteUser
и
listUsers
, поскольку они приведены в демонстрационном коде (см. Ссылки).
Вы можете убедиться в том, что это простые клиентские методы.
addUser
/** Добавляет нового пользователя в систему.
* @param prevayler где будет выполнена команда
* @param login имя пользователя для входа в систему
* @param password пароль пользователя
* @param name имя пользователя
* @param email адрес электронной почты пользователя
*/
private static void addUser(Prevayler prevayler, String login,
String password, String name, String email)
throws Exception {
System.out.println("Adding user '" + login + "'");
User u = new User();
u.setLogin(login);
u.setPassword(password);
u.setName(name);
u.setEmail(email);
AddUser cmd = new AddUser();
cmd.setUser(u);
try {
prevayler.executeCommand(cmd);
} catch (InvalidUserException e) {
System.out.println("Error: " + e.getMessage());
}
}
Листинг 7. Метод changeUser
/** Изменение данных пользователя.
* @param prevayler где будет выполнена команда
* @param login имя пользователя для изменения данных
* @param password новый пароль
* @param name новое имя пользователя
* @param email новый e-mail адрес пользователя
*/
private static void changeUser(Prevayler prevayler, String login,
String password, String name, String address)
throws Exception {
System.out.println("Changing user '" + login + "'");
User u = (User)
((UserManager) prevayler.system()).getUsers().get(login);
u.setPassword(password);
u.setName(name);
u.setEmail(address);
ChangeUser cmd = new ChangeUser();
cmd.setUser(u);
prevayler.executeCommand(cmd);
}
Превалирующие системы и Web
С Prevayler вы можете разрабатывать множество видов приложений.
Понятность и простота в использовании делает его идеальным для быстрого
моделирования web приложений. Затем вы можете пустить эти приложения в
производство с незначительными трудозатратами. Поскольку они являются простыми
классами языка Java и бинами, вы можете использовать вашу любимую среду
разработки для быстрого создания бинов и отношений между ними. Одно из
приложений, разработанных мной при помощи Prevayler, было смоделировано очень
быстро. Оно было сделано настолько хорошо, что заказчик принял решение о его
незамедлительном выпуске, даже без рассмотрения варианта использования базы
данных. Размещение приложения также весьма простая операция, не требующая
настройки серверов баз данных. Я несколько изменил правила создания резервных
копий и директорию PrevalenceBase
(где Prevayler сохраняет
сериализованные команды) в управлении резервированием. Рис. 2
показывает как web приложение может выиграть от превалирования объектов.
Рис. 2. Создание превалирующего Web
приложения
С архитектурой, изображенной на рис. 2 существует лишь одна проблема. С единственным набором бизнес объектов в каждом Web контейнере архитектура не поддерживает кластеризацию или репликацию. Одним из решений этой проблемы, которое реализовано в Prevayler сегодня, добавить общее хранение на основании отчета команд (как общие NFS, или диск общего пользования Windows). Тогда все Web контейнеры смогут прочитать одни и те же отчеты команд и снимки, расположенные на других виртуальных машинах. Реплика также может считать все команды, системы хоста и применять их в том же порядке. На время резервного сохранения реплика прекращает чтение команд и производится их безопасная съемка. Затем реплика продолжает чтение очереди команд и возвращается к синхронизации с системой хоста. Описанный подход репликации не понадобился в прототипе приложения заказчика, упомянутом мной ранее, поскольку время резервирования было синхронизовано временем съемки превалирующей системы. Таким образом, резервируется только один файл, а не весь отчет команд. На рис. 3 графически представлена репликация.
Рис. 3. Репликация внутри превалирующего Web
приложения
Команда разработчиков Prevayler скрупулезно работала над созданием лучшей системы репликации для тех, кому они необходимы. После работы над рядом приложений я с удивлением обнаружил, что главный критический момент превалирующей системы - не доступ к данным и логика манипуляций, а скорее Web сервер.
Заключение
Превалирование объектов - очень полезная концепция, которая может быть использована в приложениях различного типа. Ее простота - главное достоинство. Но простота часто отпугивает людей, которые задают мне вопросы:
- Это средство мощное?
- Оно масштабируемо?
- Какая у него производительность?
Лучший способ ответить на эти вопросы - запустить
ScalabilityTest
из пакета
org.prevayler.test.scalability
, чтоб вы все увидели собственными
глазами. С этим тестом вы убедитесь в мощи концепции превалирования
объектов.
Запомните, Prevayler продукт, распространяемый с открытым исходным кодом, а превалирование объектов - концепция, а не инструмент. Вы можете реализовать ее на других языках (что уже сделано на языках SmallTalk и Ruby). Если вы сделаете это, пожалуйста, проинформируйте команду разработчиков Prevayler, мы будем рады сотрудничать с вами!
Ссылки
- Посетите страницу Prevayler на сервере SourceForge.net
- Скачайте демонстрационный код, использованный в данной статье.
- Познакомьтесь с концепцией превалирования, разработанной Клаусом Вьюстэфилдом (Klaus Wuestefeld) и его коллегами из Objective Solutions.
- Познакомьтесь с шаблонами разработки на сервере Patterns Home Page.
- Прочитайте дискуссию на advogato.org с которого все началось.
- Посмотрите тест сериализации Варда Каннингхэма (Ward Cunningham), демонстрирующий, что превалирование объектов, по крайней мере на языке Java, не страдает от низкой производительности сериализации.
- Изучите Mnemonic Лесли Хэнсли (Leslie Hensley), реализацию превалирования объектов на языке Ruby.
- Прочитайте спецификацию Java сериализации от Sun.
- Познакомьтесь с Java API, такими как Jakarta Commons Collections и Jutil.org.
Об авторе
Карлос Эдуардо Виллела (Carlos Eduardo Villela) - 19 летний бразилец, получивший образование в области информационных систем. Он начал программировать в очень раннем возрасте. Опираясь на 8 летний опыт работы, он отдает предпочтение языкам Java и Python. В настоящее время он содержит собственную консалтинговую компанию, Stage2 Sistemas Web. Также он является владельцем и редактором Web сайта Prevayler и успешно использует Prevayler в своих проектах. Вы можете связаться с ним по E-Mail: [email protected]
Разработчик и владелец Stage2 Sistemas Web
Август 2002
Перевод Илья Чекменев