MIDAS. Практическое применение

Роман Игнатьев

Введение

Необходимые знания: Перед прочтением рекомендуется ознакомиться с технологией MIDAS хотя бы на уровне демонстрационных приложений, поставляющихся с Delphi.

Технология MIDAS (Multi-tier Distributed Application Services Suite, Сервис для создания многоуровневых распределенных приложений) была предложена фирмой Borland уже довольно давно, первое приложение с ее использованием я написал еще в 98 году, на Delphi 4. И с тех пор практически все приложения для работы с базами данных создаются мной именно на основе MIDAS. О преимуществах, думаю, говорить не надо – даже простое разделение приложения на две части, одна из которых работает с базой данных (сервер приложений), а другая обеспечивает интерфейс пользователя, создает значительные удобства как при разработке приложения, так и при его использовании.

Сейчас существует множество статей и книг по технологии создания многозвенных приложений на Delphi, но, к сожалению, мне не удалось найти литературы, в которой бы рассматривались некоторые интересующие меня вопросы. Дело в том, что во всех примерах создается трехзвенное приложение, в котором сервер приложений просто соединяет сервер БД с клиентской частью, просто абстрагируя работу с базой данных.

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

Ниже рассматривается очень простое приложение для работы с сервером БД. База данных, с которой работает это приложение, содержит всего 3 таблицы. С его помощью мне хотелось бы показать некоторые способы создания полноценного сервера приложений, обеспечивающего полную обработку данных.

Все, что написано ниже, относится к Delphi 5, в качестве сервера выбран Interbase 5.6. Именно эти продукты я использовал в большинстве проектов. Однако база данных работает и на более старших версиях Interbase, я проверял ее работоспособность, в частности, на IB6, а исходные тексты приложения с минимальными изменениями можно компилировать на старших версиях Delphi. К сожалению, некоторые изменения делать все же придется, так как MIDAS постоянно развивается. Но, как правило, такие изменения носят косметический характер, и сделать их несложно. Как изменить проект для компиляции на Delphi 6, будет рассказано в заключительной части. Несмотря на то, что в сервере приложений используются компоненты прямого доступа Interbase Express (IBX), нетрудно перейти на другой сервер БД, просто заменив компоненты доступа и немного изменив текст методов.

Исходя из практических соображений ниже описано создание трехуровневого приложения, включая разработку структуры базы данных. Мне не хотелось пользоваться готовыми примерами, входящими в поставку Interbase и Delphi, чтобы не придерживаться шаблонов, навязываемых ими.

Постановка задачи

Я собираюсь на простом примере показать возможности технологии, поэтому приложение будет работать всего с одним логическим объектом – некоторым абстрактным документом, состоящим из заголовка и содержимого. Такая структура была выбрана из-за того, что связь “мастер-таблица - подчиненная таблица” (master-detail) очень часто встречается на практике, и также часто в приложениях встречается обработка различных документов.

“Документ” здесь понимается как некоторая абстракция, напоминающая реальные документы, такие, как накладная или счет.

Разумеется, необходимо обеспечить одновременную работу с документами нескольких пользователей.

Описание документа

Заголовок документа прост, и состоит из следующих полей:

Номер – номер документа.

Дата – дата выписки, по умолчанию текущая.

Поставщик – имя, телефон.

Получатель – также имя и телефон.

Сумма – общая сумма по документу.

Поставщик и получатель будут выбираться из единого справочника клиентов, содержащего только два поля: имя и телефон.

Документ содержит записи со сведениями о перемещении неких товаров. Содержимое документа – это таблица в базе данных, записи которой состоят из следующих полей:

Порядковый номер

Номер документа, к которому относится.

Наименование - наименование товара.

Количество.

Цена.

Сумма.

Результат может выглядеть следующим образом:

номер п/п наименование количество цена сумма
1 Что-то 1 30.00 30.00
2 Что-то еще 2 10.50 21.00
...

Пользователю должен выдаваться список документов с указанием реквизитов документа и его итоговой суммы.

Сумма документа будет рассчитываться сервером приложений на основе его содержимого.

Кроме этого, создадим итоговый отчет – "шахматную" ведомость, где по одной оси расположены поставщики, а по другой – получатели:

За период с <дата> по <дата> (по дате документа)
От кого Кому
Получатель1(имя) Получатель2(имя) ...
Поставщик1(имя) Сумма Сумма
Поставщик2(имя) Сумма Сумма
...

В отчет будут входить суммы из документов, даты которых попадают в заданный период.

Отчет, разумеется, получается также достаточно оторванным от жизни, мне просто хотелось показать с его помощью дополнительные возможности MIDAS.

Блокировки

Поскольку работать с БД будут сразу несколько пользователей, важно заблокировать документ на время редактирования документа пользователем. “Почетная обязанность” синхронизации работы пользователей возлагается в данном случае на сервер приложений. В принципе, все это можно реализовать и средствами SQL сервера, но, во-первых, для сервера Interbase блокировки записей довольно неестественны, во-вторых, как будет видно ниже, сервер приложений позволяет легко блокировать сразу весь документ как единый объект.

Структура БД

Требования к приложению описаны, и теперь можно приступить к следующему этапу – созданию БД. Нам нужны три таблицы (магическое число три выбрано исключительно для простоты): для документа, содержимого документа и справочника клиентов. Документ содержит две ссылки на справочник клиентов – “Поставщик” и "Получатель", а содержимое документа имеет ссылку на документ. На рисунке 1 представлена ER-диаграмма этой БД.

MIDAS. Практическое применение

Рисунок 1. ER-модель БД.

В данной базе целостность данных обеспечивается по следующим правилам:

При упоминании в каком-либо документе поставщика или получателя удаление этой строки из справочника клиентов не допускается.

При удалении документа удаляются и все связанные с ним строки из таблицы "Содержимое документа".

Вставка в таблицу "Содержимое документа" допускается только при условии, что поле ID ссылается на существующий документ.

Как видно, в таблице "Заголовок документа" есть поле "Сумма". Это поле должно содержать полную сумму документа (сумму полей "Сумма" содержимого документа). При изменении содержимого документа приходится пересчитывать значение этого поля. Наличие такого поля, является нарушением принципов нормализации. Эту сумму всегда можно посчитать в SQL-запросе при выводе данных пользователю. Но при выдаче списка документов расчет суммы каждого из них увеличивает нагрузку на сервер БД. Отслеживать актуальность этого поля можно на триггерах СУБД, но раз уж мы создаем сервер приложений, почему бы не возложить на него эту задачу? К тому же, это обеспечивает некоторую независимость от особенностей функционирования сервера БД, что может оказаться полезным, например, при переходе на другой сервер.

Ниже приведен скрипт, создающий подопытную БД:

/* Определены следующие типы данных:

 Имя клиента

 Количество для содержимого документа

 Цена

 Сумма

*/

create domain DName varchar(180);

create domain DCount numeric(15,4) default 1 not null;

create domain DCurrency numeric(15,2) default 0 not null;

create domain DSum numeric(15,4) default 0 not null;

/* Справочник поставщиков и получателей */

create table CLIENT

(

 CLIENT_ID integer not null,

 NAME DName not null, /* Имя */

 PHONE varchar(40),  /* Телефон */

 constraint PK_CLIENT primary key (CLIENT_ID)

);

/*Заголовок документа*/

create table DOC_TITLE

(

 DOC_ID integer not null,      /* ID */

 DOC_NUM varchar(40) not null,    /* Номер */

 DOC_DATE date not null,       /* Дата */

 FROM_ID integer default 0 not null, /* Поставщик */

 TO_ID integer default 0 not null,  /* Получатель */

 DOC_SUM DSum,            /* Сумма */

 constraint PK_DOC_TITLE primary key (DOC_ID),

 constraint FK_DOC_FROM_CLIENT foreign key (FROM_ID)

 references Client (CLIENT_ID)

  on update cascade,

 constraint FK_DOC_TO_CLIENT foreign key (TO_ID)

 references Client (CLIENT_ID)

  on update cascade

);

/*Содержимое*/

create table DOC_BODY

(

 DOC_ID integer not null,    /* сcылка на заголовок */

 LINE_NUM integer not null,   /* Номер п/п */

 CONTENT varchar(250) not null, /* Наименование */

 COUNT_NUM DCount,        /* Количество */

 PRICE DCurrency,        /* Цена */

 constraint PK_DOC_BODY primary key (DOC_ID, LINE_NUM),

 constraint FK_DOC_BODY_TITLE foreign key (DOC_ID)

 references DOC_TITLE (DOC_ID)

  on delete cascade

  on update cascade

);

Скрипт создает три таблицы: CLIENT (поставщики/получатели), DOC_TITLE (документ), DOC_BODY (содержимое документа).

Следующий этап – формирование списка документов. В заголовке документа содержится только ссылка на поставщика и получателя. Вывод списка удобно организовать отдельным запросом, а в данном случае – хранимой процедурой. Пусть для удобства имя клиента в списке показывается в виде "Имя (Телефон)". Для этого сделаем процедуру CLIENT_FULL_NAME, которая извлекает эту строку, и будем вызывать ее из процедуры выдачи списка LIST_DOC. Эта же процедура пригодится для отображения имени поставщика и получателя на форме редактирования документа:

create procedure CLIENT_FULL_NAME(ID integer)

returns (FULL_NAME varchar(224))

as

 declare variable NAME varchar(180);

 declare variable PHONE varchar(180);

begin

 select NAME ,PHONE

  from client

  where CLIENT_ID = :ID

 into :NAME, :PHONE;

 FULL_NAME = '';

 if (NAME is not NULL) then

  FULL_NAME = NAME;

 if (PHONE is not NULL) then

  FULL_NAME = FULL_NAME || ' (' || PHONE || ')';

end

create procedure LIST_DOC (FROM_DATE date, TO_DATE date)

returns (DOC_ID integer, DOC_NUM varchar(40), DOC_DATE date, FROM_ID integer,

 TO_ID integer, FROM_NAME varchar(224), TO_NAME varchar(224),

 DOC_SUM numeric(15,4))

as

begin

 for select DOC_ID, DOC_NUM, DOC_DATE, FROM_ID, TO_ID, DOC_SUM

  from DOC_TITLE

  where DOC_DATE >= :FROM_DATE and DOC_DATE <= :TO_DATE

 into :DOC_ID, :DOC_NUM, :DOC_DATE, :FROM_ID, :TO_ID, :DOC_SUM

 do begin

  FROM_NAME = NULL;

  TO_NAME = NULL;

  execute procedure CLIENT_FULL_NAME (:FROM_ID)

  returning_values :FROM_NAME;

  execute procedure CLIENT_FULL_NAME (:TO_ID)

  returning_values :TO_NAME;

  suspend;

 end

end

Осталась процедура для отчета:

create procedure REP_INOUT(FROM_DATE date, TO_DATE date)

returns (FROM_ID integer, FROM_NAME varchar(180), TO_ID integer, TO_NAME varchar(180),

     FULL_SUM numeric(15,4))

as

begin

 for select FROM_ID, TO_ID, sum(DOC_SUM)

  from DOC_TITLE

  where DOC_DATE >= :FROM_DATE and DOC_DATE <= :TO_DATE

  group by FROM_ID, TO_ID

  into :FROM_ID, :TO_ID, :FULL_SUM

 do begin

  FROM_NAME = NULL;

  TO_NAME = NULL;

  select NAME

   from client

   where CLIENT_ID = :FROM_ID

  into :FROM_NAME;

  select NAME

   from client

   where CLIENT_ID = :TO_ID

  into :TO_NAME;

  if (FULL_SUM is NULL) then

   FULL_SUM = 0;

  suspend;

 end

end

Процедура выдает то, что нужно для отчета, но, к сожалению, не в виде перекрестного отчета, а по строкам:

От кого Кому На сумму
<Поставщик> <Получатель> Сумма ...
...

Приводить к нормальному виду все это будет сервер приложений.

Все готово для написания сервера приложений. Приступим.

Сервер приложений

Создаваемый нами сервер приложений будет отдельным исполняемым модулем. Этот модуль затем можно будет расположить на отдельном компьютере, который сможет производить расчеты для нескольких клиентов и синхронизировать их работу.

Сервер приложений должен обеспечивать обработку документа как единого объекта, поэтому разумным будет выделить работу с ним в отдельный класс, в данном случае потомок TRemoteDataModule. Нам также понадобится модуль данных для работы со справочником поставщиков и получателей, и выдачи списка документов. Отчет я решил также выделить в отдельный модуль. В итоге на сервере необходимо создать три потомка TRemoteDataModule: rdmCommon (общий модуль со списками поставщиков/получателей и документов), rdmDoc и rdmReport – соответственно для документа и отчета.

Мастер создания удаленного модуля данных предлагает по умолчанию политику загрузки исполняемого модуля Multiple instance и модель потоков Apartment. Это именно то, что нам нужно! Действительно, Instancing = Internal приведет к созданию серверного компонента в клиентском процессе (это распространяется только на сервер, создаваемый в виде DLL). При Single instance каждая клиентская часть будет соединяться со своим собственным экземпляром сервера приложений, а синхронизацию проще сделать, если все клиенты подсоединяются к одному экземпляру сервера приложений. Выбор модели потоков Apartment позволит избежать ручной синхронизации доступа к данным компонента.

Теперь остается создать три (опять три, это тоже случайно) потомка TRemoteDataModule, расположить на них компоненты доступа к данным и написать код для обработки данных.

При этом необходимо

Если Вам нужна помощь с академической работой (курсовая, контрольная, диплом, реферат и т.д.), обратитесь к нашим специалистам. Более 90000 специалистов готовы Вам помочь.
Бесплатные корректировки и доработки. Бесплатная оценка стоимости работы.

Поможем написать работу на аналогичную тему

Получить выполненную работу или консультацию специалиста по вашему учебному проекту
Нужна помощь в написании работы?
Мы - биржа профессиональных авторов (преподавателей и доцентов вузов). Пишем статьи РИНЦ, ВАК, Scopus. Помогаем в публикации. Правки вносим бесплатно.

Похожие рефераты: