А между тем само название технологии — Multitier Distributed Application Services Suite (набор многоуровневых сервисов приложений) — говорит о том, что это средство как нельзя лучше подходит для разработки СУБД корпоративного масштаба. Та простота, с которой создаются большие информационные системы на основе MIDAS, поможет использовать это маленькое чудо в своих целях.

MIDAS-приложение под лупой

Вопрос «А как это работает?» вполне можно отнести к разряду риторических, поскольку создатели компонентов MIDAS исходили из того, что разработчику нет никакого дела до сложностей внутреннего устройства этой технологии. Даже наоборот: чем сильнее скрыты детали реализации, тем лучше. Однако для желающих разобраться в сути явлений поясним. Типичная система подобного рода разбита на три слоя. Самый последний слой, где располагается сервер баз данных и иже с ним, нас не интересует, поскольку он традиционен. В поле нашего зрения попадают другие два слоя: клиентский и слой бизнес-логики. Обратите внимание, что и на том и на другом должна быть установлена библиотека MIDAS.DLL.

Клиентский слой

На первом слое, клиентском, располагается интерфейс, составленный из визуальных компонентов VCL, работающих с данными (data-aware components). Это очень похоже на СУБД, созданные с применением традиционных средств Borland Delphi и C++Builder c компонентом — источником данных. Различия скрыты в компоненте хранения и обработки набора данных. В простом (не-MIDAS) случае это либо компонент-таблица, либо компонент выполнения запроса SQL. В распределенном же (многозвенном) приложении компоненты этого класса вынесены на следующий, промежуточный, слой, где располагается бизнес-логика, разделяемая между всеми клиентами системы. А вместо них на клиентском слое обнаруживаются два других компонента: TClientDataSet и компонент соединения с удаленным сервером.

TClientDataSet — довольно интересная вещь. Этот компонент служит своеобразным кэшем для полученных с удаленного сервера данных. Он столь многофункционален, что с тем же успехом может считывать данные и из «плоской» таблицы, хранящейся в файле на диске. Немаловажную роль в универсальности TClientDataSet играет тот факт, что предком этого компонента является класс TDataSet, от которого наследуются и другие компоненты хранения и обработки набора данных. С точки зрения MIDAS компонент TClientDataSet интересен тем, что где-то в своих недрах он с помощью специального интерфейса IAppServer общается с удаленным модулем данных, о котором чуть позже.

Компоненты соединения с удаленным сервером заведуют связью между программой-клиентом и слоем бизнес-логики. В Delphi 5 таких компонентов четыре. Каждый из них выполняет соединение своим способом. Например, компонент TDCOMConnection для передачи и получения данных использует возможности технологии Microsoft DCOM, тогда как TSocketConnection — секреты и протокол TCP/IP. Если бизнес-логика выполнена на основе CORBA, то для связи предусмотрен компонент TCORBAConnection, коммуникационным протоколом для которого является IIOP. Несколько особняком стоит TWebConnection — новинка Delphi 5. Он организует «перекачку» данных с помощью популярного в Internet протокола HTTP. Короче, MIDAS предлагает соединения на все случаи жизни. Еще один компонент, TOLEnterpriseConnection, не в счет — он служит лишь для обеспечения совместимости с предыдущими версиями MIDAS.

Слой бизнес-логики

Написать клиентское приложение — это лишь полдела. Важно обеспечить работу на промежуточном слое, поскольку именно здесь происходит реальное управление данными по запросам, приходящим от удаленных приложений-клиентов. Здесь выполняется сервер приложений, управляемый интерфейсом IAppServer.

С точки зрения разработчика, промежуточный слой — всего лишь удаленный модуль данных (Remote Data Module), создание которого до боли похоже на генерацию обычных модулей данных, знакомых программистам на Delphi и C++Builder. Удаленный модуль данных — это компонент, выполненный по технологии COM/DCOM, c определенным набором COM-интерфейсов.

В технологии MIDAS есть удаленные модули данных трех видов: Remote Data Module, MTS Data Module и CORBA Data Module. Первый из них обеспечивает взаимодействие с клиентами по протоколам DCOM, TCP/IP и HTTP. Второй, как видно из названия, аналогичен первому, но использует возможности популярного ныне сервера транзакций Microsoft Transaction Server. Последний вид модулей служит для реализации бизнес-логики в соответствии с технологией CORBA. Сразу оговоримся, что в этой статье мы не будем рассматривать удаленные модули MTS, поскольку это отдельная область программирования. А вот CORBA Data Module коснемся обязательно.

Заметим, что удаленные модули данных создаются с помощью мастеров, расположенных на закладке Multitier диалоговой панели, появляющейся при выборе из меню команды File?New.

Внутреннее взаимодействие

Итак:

1. Пользователь запускает клиентское приложение, которое пытается связаться с сервером приложений; если сервер приложений не был запущен, то он запускается; клиентское приложение получает интерфейс IAppServer от сервера приложений.

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

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

4. Клиентское приложение распаковывает данные и в каком-то виде показывает пользователю.

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

6. Для изменения данных протокол пакуется как данные и посылается серверу приложений.

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

8. Клиентская программа или отбрасывает невозможные изменения или же пытается исправить их, после чего предпринимается еще одна попытка «уговорить» сервер приложений внести уже скорректированные изменения.

9. Клиентское приложение обновляет свои данные, запрашивая у сервера новую выборку.

Небольшое отклонение в сторону. Delphi 5 имеет в своем арсенале заготовку диалоговой панели Reconcile Error Dialog для интерактивного внесения изменений в данные в том случае, когда нужно согласовать изменения. Она располагается на закладке Dialogs диалоговой панели, появляющейся при выборе из меню команды File?New.

Как создаются MIDAS-приложения

Не мудрствуя лукаво, создадим собственное приложение на основе компонентов технологии MIDAS, ознакомившись по пути с новыми компонентами доступа к СУБД IB DataBase (на Западе именуемой InterBase). Данный набор компонентов впервые появился в Borland Delphi 5.

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

Проект нашей программы будет множественным, т. е. будет представлять собой целую группу проектов.

Создание сервера

Обычно MIDAS-приложения начинают создавать с сервера приложений. Для этого следует воспользоваться мастерами генерации удаленных модулей данных. Первый сервер, который мы создадим, будет выполнен на основе обычного удаленного модуля данных.

Создадим новый проект и, нажав комбинацию клавиш ++, откроем окно опций (Project Options). Раз уж нам предстоит создать несколько составных частей приложения, каждая из которых представляет собой отдельный проект в группе, сэкономим дисковое пространство, используя опцию компиляции с библиотечными пакетами. Для этого включим отмечаемую кнопку Build with runtime packages. Теперь, нажав ++, откроем окно Project Manager. Щелкнем правой кнопкой мыши на проекте и командой Save сохраним проект со всеми его составляющими. Модулю формы присвоим имя Main.pas, сам проект сохраним как RDM_Server.dpr, а глобальную группу наших проектов — как MIDAS_Projects.bpg. Небольшие изменения нужно внести и в форму, которая автоматически добавляется в новый проект при его создании. В поле свойства Caption, отвечающего за текст заголовка, введем «Remote Data Module», а саму форму назовем MsgForm. Внутрь формы поместим индикатор прогресса (компонент TProgressBar) из палитры Win32. В принципе, форма на сервере нам не нужна. Но раз уж она получена от Delphi в качестве бесплатного пирожка, то используем ее для мониторинга общения удаленного модуля с клиентом. Кроме того, форма MsgForm берет на себя такую трудную задачу, как обработка сообщений в бесконечном цикле, что необходимо для функционирования сервера.

Теперь запустим мастер генерации удаленного модуля данных New?Multitier?Remote Data Module. Ранее было отмечено, что подобного рода модули в Delphi (как, впрочем, и в C++Builder) реализуются в виде COM-серверов. Поэтому кроме имени самого модуля (назовем его RDM) необходимо ввести в поле Instancing число экземпляров модуля, которое может создавать сервер, а в поле Threading Model указать поточную модель. В нашем случае следует установить их как Multiple instance и Apartment соответственно. Хотя, по правде говоря, создаваемый COM-сервер является Out-of-process и нечувствителен к модели.

Займемся реализацией механизма доступа к СУБД, т. е. созданием связи между средним и последним слоями MIDAS-приложения. Изначально мы договорились, что будем использовать новые компоненты VCL для прямой работы с IB DataBase. Поэтому переключим палитру компонентов на закладку InterBase и убедимся, что на компьютере установлена или имеется в сети копия IB DataBase (в обычной поставке — InterBase).

На форму модуля данных в панели Components следует положить три компонента: TIBDataBase, TIBQuery и TIBTransaction. В отличие от обычных компонентов доступа к данным, новый набор «строительных кубиков» работает исключительно через транзакции, поэтому нам и требуется компонент TIBTransaction. Еще один компонент мы возьмем из палитры Midas, и называется он TDataSetProvider. Это ключик, который открывает путь клиентскому приложению в мир серверного зазеркалья. В паре с компонентом создания соединения на клиентской стороне они образуют канал передачи информации и управления ею. Как устроен созданный нами удаленный модуль данных, видно на рис. 1. Обратите внимание, что структура модуля показана в левой панели. Если компоненты находятся в пассивном состоянии и не подключены к СУБД, Delphi отмечает их красными кружками, чтобы разработчик чего-нибудь не упустил.

Рис. 1

Перейдем к этапу настройки. Соединим между собой компоненты доступа к данным. Для этого существуют два способа. Первый заключается в установке соответствующих свойств в окне инспектора объектов (прием, характерный для более ранних версий Delphi). Если же вы обладаете последней, пятой версией этого пакета, то можете выстроить графическую модель данных, переключив форму удаленного модуля данных в режим Data Diagram. В этой панели с помощью мышки установите графическим способом все нужные межкомпонентные связи (отношения Master-Detail, Lookup). Однако нам потребуется связь на уровне свойств, для чего перетащите все компоненты из левой панели структуры данных в правую панель и, удерживая клавишу , нажмите самую верхнюю кнопку меню панели Data Diagram. После этого «протяните» связи между компонентами. Опытный разработчик СУБД знает, от какого компонента к какому следует устанавливать соединение. Если же вы не знаете толком, что и с чем связать, то ничтоже сумняшеся «тяните» мышью от одного компонента к другому, причем как в одну, так и в другую сторону. Delphi 5 интеллектуален до невозможности: если сделанная связь разрешена, она отобразится в виде стрелки, и рядом появится имя свойства, через которое эта связь была установлена. Если же соединить компоненты не представляется возможным, Delphi прямо заявит об этом. Так, перепробовав все возможные соединения, вы все равно получите правильное подключение к СУБД. Признаком того, что вы ничего не забыли, будет отсутствие в панели Data Diagram вопросительных знаков на прямоугольничках компонентов и исчезновение красных кружков возле имен компонентов в левой панели структуры модуля данных (рис. 2).

Рис. 2

Осталось настроить компоненты SQL-запроса (с этим вы справитесь сами) и базы данных. Щелкните правой кнопкой мыши на TIBDataBase и выберите пункт Database Editor контекстного меню. В появившейся диалоговой панели нужно настроить связь с СУБД. Если вы не хотите, чтобы каждый раз, когда запускается сервер, у вас спрашивали имя и пароль, снимите выделение с кнопки Login Prompt и введите другие нужные параметры (см. рис. 3).

Рис. 3

Не забудьте включить свойство Connected компонента TIBDataBase и Active компонента TIBQuery.

Ранее мы говорили о некоторой полезности сделанной по умолчанию формы. Что же, пора и ее пристроить. Поскольку мы уже добавили в нее компонент TProgressBar, то сделаем так, чтобы каждый раз, когда происходит чтение данных, шкала прогресса увеличивалась. Для этого напишем обработчик события AfterGetRecords компонента TDataSetProvider. В листинге 1 приведены все необходимые исходные тексты формы, в том числе и наш обработчик. Он прост: значение текущего указателя компонента TProgressBar инкрементируется, и если оно переваливает за максимально возможное, то сбрасывается на ноль.

Остается только сохранить, откомпилировать и запустить сервер. Запуск необходим для того, чтобы COM-сервер зарегистрировал себя в системном реестре и мог запускаться автоматически.

Окончание в следующем номере.

Простой сервер

Файл Main.dfm
object MsgForm: TMsgForm
  Left = 202
  Top = 204
  Width = 266
  Height = 73
  Caption = ?Remote Data Module?
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -13
  Font.Name = ?MS Sans Serif?
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 120
  TextHeight = 16
  object ProgressBar1: TProgressBar
    Left = 10
    Top = 10
    Width = 236
    Height = 20
    Min = 0
    Max = 20
    Step = 1
    TabOrder = 0
  end
end

Файл RDM_Impl.dfm

object RDM: TRDM
  OldCreateOrder = False
  Left = 157
  Top = 219
  Height = 352
  Width = 602
  object DataSetProvider1: 
TDataSetProvider
    DataSet = IBQuery1
    Constraints = True
    AfterGetRecords = 
DataSetProvider1AfterGetRecords
    Left = 50
    Top = 15
  end
  object IBDatabase1: TIBDatabase
    Connected = True
    DatabaseName = ?D:BorlandInterbase
examplesdatabase
Intlemp.gdb?
    Params.Strings = (
      ?user_name=SYSDBA?
      ?password=masterkey?)
    LoginPrompt = False
    DefaultTransaction = IBTransaction1
    IdleTimer = 0
    SQLDialect = 1
    TraceFlags = []
    Left = 50
    Top = 90
  end
  object IBTransaction1: TIBTransaction
    Active = True
    DefaultDatabase = IBDatabase1
    Left = 150
    Top = 90
  end
  object IBQuery1: TIBQuery
    Database = IBDatabase1
    Transaction = IBTransaction1
    Active = True
    CachedUpdates = False
    SQL.Strings = (
      
        ?select EMP_NO, FIRST_NAME, 
LAST_NAME, DEPT_NO,
JOB_CODE, SALARY ? +
        ?from EMPLOYEE?)
    Left = 50
    Top = 155
    object IBQuery1EMP_NO: TSmallintField
      FieldName = ?EMP_NO?
      Required = True
    end
    object IBQuery1FIRST_NAME: TIBStringField
      FieldName = ?FIRST_NAME?
      Required = True
      Size = 15
    end
    object IBQuery1LAST_NAME: TIBStringField
      FieldName = ?LAST_NAME?
      Required = True
      Size = 25
    end
    object IBQuery1DEPT_NO: TIBStringField
      FieldName = ?DEPT_NO?
      Required = True
      Size = 3
    end
    object IBQuery1JOB_CODE: TIBStringField
      FieldName = ?JOB_CODE?
      Required = True
      Size = 5
    end
    object IBQuery1SALARY: TFloatField
      FieldName = ?SALARY?
    end
  end
end

Файл RDM_Impl.pas

unit RDM_Impl;

interface

uses
  Windows, Messages, SysUtils, 
Classes, ComServ, ComObj,
VCLCom, DataBkr,
  DBClient, RDM_Server_TLB, StdVcl, 
 Db, IBCustomDataSet,
IBQuery,
  IBDatabase, Provider, Main;

type
  TRDM = class(TRemoteDataModule, IRDM)
    DataSetProvider1: TDataSetProvider;
    IBDatabase1: TIBDatabase;
    IBTransaction1: TIBTransaction;
    IBQuery1: TIBQuery;
    IBQuery1EMP_NO: TSmallintField;
    IBQuery1FIRST_NAME: TIBStringField;
    IBQuery1LAST_NAME: TIBStringField;
    IBQuery1DEPT_NO: TIBStringField;
    IBQuery1JOB_CODE: TIBStringField;
    IBQuery1SALARY: TFloatField;
    procedure 
DataSetProvider1AfterGetRecords(Sender:
TObject;
      var OwnerData: OleVariant);
  private
    { Private declarations }
  protected
    class procedure UpdateRegistry
(Register: Boolean; const
ClassID, ProgID: string); override;
  public
    { Public declarations }
  end;

implementation

{$R *.DFM}

class procedure TRDM.UpdateRegistry
(Register: Boolean; const
ClassID, ProgID: string);
begin
  if Register then
  begin
    inherited UpdateRegistry(Register, 
ClassID, ProgID);
    EnableSocketTransport(ClassID);
    EnableWebTransport(ClassID);
  end else
  begin
    DisableSocketTransport(ClassID);
    DisableWebTransport(ClassID);
    inherited UpdateRegistry
(Register, ClassID, ProgID);
  end;
end;

procedure TRDM.
DataSetProvider1AfterGetRecords
(Sender:
TObject;
  var OwnerData: OleVariant);
begin
  with MsgForm.ProgressBar1 do
  begin
    If Position < Max then
      StepIt
    else
      Position := 0;
    end;
end;

initialization
  TComponentFactory.Create(ComServer, TRDM,
    Class_RDM, ciMultiInstance, tmApartment);
end.