А между тем само название технологии — 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.