В этой серии статей мы постараемся подробно рассмотреть основные аспекты использования данной связки. Мы применили этот комбайн для реализации одной из подзадач проекта по разработке интеллектуальной системы автоматизированного управления учебным планированием. Для лучшего понимания, стоит сказать несколько слов о самом проекте.
Любые незначительные изменения в учебном плане приводят к огромным трудозатратам на его переработку и согласование в службах университета, поэтому заведующие кафедрами неохотно идут на их изменение. Целью проекта является создание агента заведующего кафедрой, который позволит создавать учебный план и вносить в него изменения таким образом, чтобы пользователь не ощущал груз рутинной работы перепроектирования учебного плана в случае внесения определённых корректировок.
По задумке, к каждому участнику процесса формирования учебного плана (например заведующему кафедры) привязывается агент, являющийся помощником и консультантом. В качестве инструмента, позволяющего легко реализовать таких агентов и обработать их поведение, была выбрана платформа JADE (Java Agent Development Framework).
В качестве СУБД, была использована Caché компании InterSystems. СУБД была выбрана нами по нескольким причинам. Во-первых, данная СУБД поддерживает объектный подход доступа к данным, что значительно облегчает работу (не считая еще и того, что она позволяет создавать проекции классов на различных языках, но об этом будет рассказано ниже). Во-вторых, в этой системе легко настроить области доступа для пользователей, что пригодится в дальнейшем. Ну и наконец, компания InterSystems всячески поддерживает разработчиков, которые пользуются их продуктами, а также предоставляет лицензии для ВУЗов бесплатно.
Было принято решение сделать систему кроссплатформенной, для удобства и мобильности в дальнейшем использовании. Поэтому, для реализации web-интерфейса системы, была выбрана flex-технология (расширяющая базовые возможности flash).
В итоге, на первом этапе разработки был выделен ряд задач, о которых в дальнейшем и пойдет речь (собственно эти задачи мы и рассмотрим в данной серии статей):
- разработка модели классов Cache
- реализация взаимосвязи между Cache и Java
- разработка web-интерфейса
- реализация взаимосвязи Java с web-интерфейсом
Для решения четвертой задачи был выбран фрэймворк GraniteDS, работа с которым будет подробнее описана в следующей статье.
В итоге получившуюся архитектуру можно представить следующим образом:
Кроме описанного выше, на клиенте устанавливается связующий агент, обеспечивающий постоянную связь с агентом JADE и информирующий пользователя о происходящих событиях и сообщениях, передаваемых основным агентом.
В первой части мы сфокусируем наше внимание на взаимодействии Cache -Java.
Рассмотрим фрагмент структуры БД Caché, используемый для функционирования агента заведующего кафедрой в мультиагентной системе управления учебным планированием (МАС УУП). Хранимые классы были спроектированы и реализованы в студии Caché.
Фрагмент базы данных Caché для агента заведующего кафедрой состоит из шести хранимых классов, основным из которых является класс дисциплин (cDiscipline), включающий все необходимые характеристики дисциплины для составления учебного плана. Из приведенной выше диаграммы видно, что дисциплины разделяются по циклам и семестрам, которые, в свою очередь, входят вместе с дисциплинами в состав учебного плана по определённому направлению. Дисциплина может являться базовой и иметь в своём составе несколько других дисциплин, что реализовано отношением Parent. Связь дисциплины с классом cControlForm определяет форму контроля по дисциплине (экзамен или зачёт). Также учебный план содержит список внесенных в него изменений (класс cLogs).
Для последующей реализации связки хранимых классов Caché с логикой приложения на java, с помощью стандартного механизма проецирования, были сгенерированы классы java-проекций. Ниже представлен код создания java-проекции в классе Caché.
Projection PrjName As %Projection.Java(ROOTDIR = "c:Out");
//PrjName – имя проекции, ROOTDIR – путь вывода класса на java
Как было отмечено, организация связи с базой данных Caché выполнена с использованием механизма java-проекций, а также библиотек, поставляемых с Caché для связи через JDBC.
Для облегчения разработки на данном этапе был создан слой m-классов, позволяющий отвязаться от классов java-проекций (c-классов).
Зачем это сделано? Классы java-проекций для описываемой системы, генерируемые Caché, представлены на данной диаграмме в виде пакета классов «Java c-classes». В пакете «Java m-classes» содержаться классы, каждый из которых соответствует классу из пакета «Java c-classes». Java m- классы повторяют только свойства классов из пакета c-, и помимо этого содержат свои get() и set() методы. Такой подход удобен при постоянных изменениях в классах Caché, поскольку после малейшего изменения в Caché, достаточно заново сгенерировать Java-проекцию необходимого класса Caché и затем произвести замену (а изменять m-классы значительно проще, чем редактировать вновь созданную проекцию). Кроме того этот подход на данном этапе помогает с организацией микротранзакций. К примеру, позволяет легко откатить изменения в m- классах, которые еще не были сохранены в c- (в частности необходимо для алгоритма). А если учесть еще и то, что Java напрямую не поддерживает клонирование, такие микротранзакции значительно облегчают работу. Помимо этого, Granite DS для создания ActionScript проекций требует внесения в основной класс некоторых изменений (об этом будет рассказано во второй части статьи), а внести такие изменения в m- класс легче, чем переписывать частично структуру c- класса. Поэтому такой подход значительно облегчает работу на основном этапе разработки, в то время как, на этапе оптимизации это решение может быть переработано.
Так, к примеру, с точки зрения эффективности и памяти этот подход выглядит не очень привлекательно. И в дальнейшем от него, скорее всего, придется отходить за счет реализации в с- классах интерфейса Serializable, который необходим для работы Granite DS.
Класс CacheTransform является связующим звеном между базой данных Caché и java классами. В этом классе реализуется два пути работы с БД Caché: через JDBC и через java-проекции. Для обеспечения связи с БД через JDBC в классе CacheTransform реализована функция подключения к базе данных. Реализация этой функции использует объект класса Database, поставляемого в библиотеке cachedb. Ниже приведён код функции подключения к БД.
public Boolean InitCon(String port, String user, String pass)
{
try {
username=user;
password=pass;
url="jdbc:Cache://localhost:"+port+"/IKIT";
dbconnection = CacheDatabase.getDatabase(url, username, password);
connect=true;
try{
setParams(port,user,pass);
}catch (IOException er){System.out.println("[Error of IO]: "+er);}
return true;
} catch (CacheException e) {
e.printStackTrace();
return false;
}
}
Функция InitCon принимает основные параметры подключения (port – номер порта, user – имя пользователя, pass – пароль для подключения к Caché), заносит их в соответствующие глобальные переменные и пытается подключиться к базе данных. При успешном подключении текущие параметры заносятся в конфигурационный файл функцией setParams(port,user,pass). Также, стоит отметить, что url использует localhost, поскольку само java-приложение и БД расположены на одной машине и в дальнейшем стоит унифицировать эту функцию, чтобы она принимала как адрес машины, на которой работает БД, так и область БД (в данном примере область — IKIT).
Далее рассмотрим порядок считывания данных из БД Caché. В классе CacheTransform для каждого класса java-проекции реализована пара функций, одна из которых загружает один объект класса по Id, а другая все объекты класса. Ниже приведён
///Функция загрузки одного объекта формы контроля
public mControlForm ReadOneCont(Integer ind)
{
try {
if(connect == true)
{
cControlForm temp = (cControlForm) cControlForm._open(dbconnection, new Id(ind));
return TranformCF(temp);
}
return null;
}
catch (CacheException e) {
e.printStackTrace();
return null;
}
}
///Функция загрузки всех объектов форм контроля
public void ReadAllCont()
{
try {
if(connect == true)
{
ListOfCF.clear();
Iterator k = dbconnection.openByQuery("SELECT
IKIT.cControlForm.%ID FROM IKIT.cControlForm");
while (k.hasNext())
{
ListOfCF.add(TranformCF((cControlForm)k.next()));
}
}
}
catch (CacheException e) {
e.printStackTrace();
}
}
Из примера видно, что для загрузки одного объекта по Id используется функция _open класса java-проекции, а для загрузки всех объектов вызывается функция openByQuery объекта dbconnection класса Database, в которую передаётся SQL запрос. Стоит отметить, что IKIT.cControlForm.%ID возвращает коллекцию объектов. Далее в обеих функциях вызывается метод TranformCF:
///Трансформация Формы контроля
public mControlForm TranformCF(cControlForm cur)
{
try{
mControlForm res=new mControlForm();
res.setId(Integer.parseInt(cur.getId().toString()));
if(cur.getZET()!=null) res.setZet(cur.getZET().floatValue());
res.setName(cur.getName());
return res;
}
catch (CacheException e) {
e.printStackTrace();
return null;
}
}
Как видно из описания, данный метод принимает объект c- класса и в случае успешного преобразования возвращает объект m- класса. В частности данный метод работает c классом ControlForm.
Порядок сохранения данных в БД Caché аналогичен процессу загрузки и реализован также в классе CacheTransform. Ниже приведён пример для сохранения данных класса cControlForm.
///Трансформация Формы контроля
public cControlForm RevTranformCF(mControlForm cur, Integer act)
{
try {
cControlForm res = null;
if (act == 1)
{
System.out.println("//MAS: TRY EDIT CF: "+ act.toString() + "nID:" + cur.getId().toString());
res = (cControlForm) cControlForm._open(dbconnection, new Id(cur.getId()));
}
else {
System.out.println("//MAS: TRY ADD CF");
res = new cControlForm(dbconnection);
}
res.setName(cur.getName());
if(cur.getZet() != null) res.setZET(cur.getZet().doubleValue());
res._save();
return res;
}
catch (CacheException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
return null;
}
}
В примере входной аргумент act функции RevTranformCF принимает значение 1 или 0. Когда act равен единице, из базы данных загружается объект класса cControlForm, в противном случае, создаётся новый объект класса cControlForm. Далее выполняется присвоение значений атрибутам объекта cControlForm и вызывается функция сохранения.
Вызов методов объекта класса CacheTransform выполняется в классе UserService, который реализует интерфейс IUserService, используемый в свою очередь классами ActionScript web-приложения. Подробнее о данной связке будет рассказано в следующей части статьи.
Подводя итоги данной части статьи, можно сказать, что в нашем случае работа с Java-проекциями Cache значительно облегчила работу, поскольку она позволяет обойти связку «Cache класс»-«Данные»-«Java класс», поручая основную работу с данными СУБД. Проекции удобнее и с точки зрения ООП, так как происходит непосредственная работа с классами и отпадает необходимость в создании вручную структуры java-классов для обработки данных. Так, к примеру, вместо SQL запросов, зачастую возвращающих данные, тип которых необходимо знать заранее, используются методы get(), set(), позволяющие получить типизированные данные. Помимо всего прочего, регенерация классов-проекций при компиляции класса Cache избавляет от необходимости вносить значительные изменения в Java-классы, что наблюдалось бы при использовании связи через JDBC.
Что же касается основной структуры, то на этапе оптимизации потребуется унифицировать функции чтения и записи, а также перейти от дубликатов к чистым проекциям.
В следующей части статьи мы расскажем об общей структуре проекта, реализующего связи Caché -Java — Flex, механизмах взаимодействия, а также используемых для этого инструментах.
Автор: yakuninyy