Продукты
Delphi Client/Server – народный компилятор для построения сложных систем
Владимир Водолазский
От редакции: Статья дает представление об особенностях Delphi и вообще о системах разработки приложений с архитектурой "клиент-сервер". При этом, честное слово, мы вырезали из нее гораздо больше боевых воплей племени, к которому принадлежит автор, чем их осталось.
"Паскаль – это язык для любителей машинописи. Настоящие программисты пишут на ассемблере"
Из свода правил для хакеров
О технологии программирования "клиент-сервер" сегодня много спорят и говорят. Но дело не ограничивается только праздной болтовней. На рынке появляется все больше и больше систем, предназначенных для облегчения кропотливого труда разработки распределенных приложений, которые зачастую ориентированы на работу в гетерогенных вычислительных средах. Что только не идет в ход! И визуальное программирование, и диаграммы, позволяющие наглядно представить структуры сложных баз данных, и средства удаленной отладки – в общем, всего не перечесть...
Давайте поговорим о том, что же нового появилось в программировании систем "клиент-сервер" с выходом на рынок системы Delphi. Начнем с освещения проблемы взаимодействия с базами данных. Но вначале очень коротко об одном "подводном камне". Конечно же, о том, что такое "клиент-сервер", сегодня знают все. Я не рискну в очередной раз повторять общеизвестные вещи (см. [1]). Коснусь только одного заблуждения, которое получило слишком широкое распространение.

Каким образом осуществляется взаимодействие между приложениями? Традиционный ответ сводится к схеме из двух квадратиков (рис. 1). Между тем, как показывает практика, подобное представление говорит о чрезмерном оптимизме отвечающего. Потому что жизнь, великий учитель, вносит в эту схему свои коррективы. И, как было разъяснено на конференции разработчиков Borland в июле прошлого года (см. [2]), немаловажную роль здесь играет сеть, через которую осуществляется взаимодействие между клиентской и серверной частями приложения. То есть говорить надо о "триединой" системе, представленной на рис. 2. Попытка создания эффективно работающей системы "клиент-сервер" без учета особенностей используемой сети (будь то локальная вычислительная сеть или распределенная) обречена на поражение.

В системе Delphi Client/Server входящие в комплект поставки SQL-интерфейсы обеспечивают связь с большинством современных систем управления базами данных, таких как InterBase, Oracle, Sybase, Informix. Каждая из них представляет собой сервер, реализованный для большого количества платформ операционных систем, поддерживающих различные сетевые протоколы – CP/IP, SPX, NetBEUI, Named Pipes и так далее. Но ведь необходимо не только предоставить серверу информацию в требуемом ему формате, но и передать данные через сеть! Это подводит нас к рассмотрению вопроса о том,
Как Delphi осуществляет обмен с базами данных
Программисты Borland подготовили и выпустили в свет универсальную систему поддержки баз данных – Borland Database Engine (BDE). Она дает программисту набор средств, обеспечивающих прозрачный доступ к данным вне зависимости от их физической структуры. Это оказывается возможным за счр\ч поддержки двух наиболее распрях страненных интерфейсов баз данных – IDAPI (Borland) и ODBC (Microsoft). В Delphi это решение получило дальнейшее развитие, что привело к появлению двух групп компонентов – Database Access и DataBase Controls.
В результате программисту достаточно освоить только работу с BDE и сконфигурировать драйверы. BDE самостоятельно по мере необходимости загружает в оперативную память драйверы серверных СУБД и конфигурирует их для работы с той или иной сетевой операционной системой.
Драйверы серверов состоят из двух частей – загружаемые вне зависимости от конфигурации сети и ориентированные на специфику того или иного сетевого протокола. После завершения процесса установки драйвер сервера отправляет сообщение серверу базы данных с использованием выбранного сетевого протокола.
Отметим, что механизмы доступа к данным, реализованные в Delphi, позволяют обеспечить эффективный доступ как к локальным, так и к удаленным базам данных (серверам). Это и позволяет говорить о Delphi как об инструменте для создания приложений "клиент-сервер". Давайте рассмотрим пример создания подобного приложения, использующего особенности того или иного сервера. Чтобы вы могли без проблем проверить справедливость наших утверждений, мы воспользуемся локальным сервером InterBase, который входит в комплект поставки как Delphi Desktop, так и Delphi Client/Server.
Конфигурирование синонимов в ВОЕ
Слово alias на русский язык чаще всего не переводят, но ясности от этого не появляется. По-моему, более удачен термин "синоним". Под синонимами в ВОЕ подразумевается ссылка на список, в котором хранятся параметры, необходимые для налаживания и поддержания соединения с той или иной базой данных. На рис.3 приведен внешний вид программы BDE Config при создании синонима для базы данных по авторам, готовящим статьи для журнала.
Если вы окажетесь в затруднении, какие именно параметры надлежит установить, воспользуйтесь содержимым синонима IBLOCAL, который в любом случае есть в вашей системе. Один из наиболее важных параметров, который определяет стратегию поведения BDE в распределенной системе, – это SQLPASSTHRU. Значение его показывает, каким образом BDE обрабатывает запросы к удаленному серверу базы данных. Дело в том, что BDE в состоянии сама сгенерировать SQL-запрос, но не запрещает делать это и пользователю (то есть программе, которая работает с базой данных через BDE). Поле может принимать одно из трех указанных ниже значений [3].

NOT SHARED-SQL-запросы, передаваемые на удаленный сервер, и SQL-запросы, генерируемые BDE, используют независимые подключения к удаленному компьютеру. В результате этого транзакции BDE и пользователя друг с другом не конфликтуют, но ресурсов у сервера отбирается побольше.
SHARED NOAUTOCOMMIT-запросы пользователя и запросы BDE используют одно и то же подключение к удаленной машине. Снос буферов (COMMIT) после каждого оператора не выполняется. Система работает максимально быстро, но возможны конфликты между двумя механизмами доступа к данным.
SHARED AUTOCOMMIT – запросы пользователя и BDE, как и в предыдущем случае, пользуются одним и тем же подключением, но каждый оператор SQL немедленно отрабатывается (то есть выполняется операция COMMIT) – естественно, до тех пор, пока не будет начата обработка блока транзакции.
Почему этот параметр важен в нашем конкретном случае? Дело в том, что локальный сервер InterBase не разрешает осуществлять двойное подключение к системе. Это в полном смысле слова однопользовательская и однозадачная СУБД. Поэтому для отладки программного обеспечения вы должны использовать один из режимов SHARED.
Не забудьте сохранить внесенные изменения (File/Save), и можно приступать к созданию базы данных. Для этого воспользуемся программой Interactive SQL, которая входит в комплект поставки Delphi. На рис.4 показано, что вам надо ввести для создания базы данных.
Хочу обратить ваше внимание на имя пользователя и пароль. По умолчанию созданием баз данных занимается системный администратор (SYSDBA), который хранит свой пароль в тайне от непосвященных. Увы, в документации об этом ни слова. А не зная пароля, вы не сможете ни создать новую базу данных, ни внести в список новых пользователей, ни получить доступ к уже существующим базам данных. Поэтому позвольте сообщить вам страшное секретное слово – MASTERKEY.
Только после того, как вы введете пароль, кнопка ОК в диалоговом окне станет активной и позволит вам начать попытку создания базы данных. Если вы ввели неверный пароль, то программа вернет соответствующее сообщение: SQLResult= -902.
Заглянем в каталог, в котором мы создавали базу данных. Вроде бы всего лишь пустая "болванка", а сколько места на диске занимает! Более 200 Кбайт! То ли еще будет...
Не стоит расстраиваться по пустякам. В этом файле (напомню, что InterBase обычно хранит все таблицы и индексы в одном файле) уже зарезервировано пространство для хранения триггеров, генераторов, внутренних процедур и прочих атрибутов современной технологии баз данных.
Теперь нам необходимо создать одну или несколько таблиц, в которых и будут храниться данные этой базы. С этой несложной задачей вы сможете справиться самостоятельно, воспользовавшись либо программой Database Desktop, либо уже знакомой вам ????? Давайте ограничимся одной таблицей, в которой будут храниться данные об авторах, представивших свои творения. Вот ее структура, полученная с помощью программы ISQL:
Вам остается только добавить уникальный индекс к полю ID, которое представляет собой номер записи в таблице AUTHOR, что сделать несложно:
CREATE UNIQUE INDEX AUTHOR ID ON AUTHOR(ID);
Теперь вы можете либо продолжать наращивать структуру базы данных, добавив, например, генератор и триггер, обеспечивающие автоматическое создание уникального значения ключевого поля при введении новой записи, либо оставить выполнение этой операции Delphi и скорее начать работать с базой.


TDataBase – связующее звено между клиентом и сервером
Процесс оптимизации давайте отложим до следующего раза. В самом деле, нелепо пытаться повторить в краткой журнальной статье все то многообразие возможностей, которому посвящено более 1000 листов документации, а также несколько книг с приложениями на CD-ROM.
Создать новую форму вы сумеете самостоятельно. Доступ к базам данных, подобным InterBase, осуществляется с помощью специального компонента – TDataBase. Давайте начнем с того, что внесем в наш проект экземпляр этого компонента и посмотрим на его поля (рис.5).

На что стоит обратить внимание? Во-первых, на свойства DriverName и AliasName. Это взаимоисключающие свойства, то есть вы должны определить либо одно из них, либо другое. В первом случае вы будете строить свой объект, опираясь на драйвер, зарегистрированный в ВОЕ (то есть отказываетесь от использования концепции синонимов). Во втором случае вы указываете только синоним базы данных, что обеспечивает максимальную степень переносимости ваших продуктов. При установке разработанного вами продукта в другой системе вам потребуется только указать другой путь в описании синонима (с помощью BDEConfig), не переписывая ни строчки кода программы. При этом совершенно неважно, будете ли вы работать с локальной базой данных или с удаленным UNIX-сервером!
Следующее важное свойство – DataBaseName. Оно определяет внутренний синоним, по которому таблицы и запросы вашей программы будут обращаться к базе данных. Приоритет локального синонима выше, чем у глобального. Вот простой пример. Если вы попытаетесь создать компонент Ttable и назначить ему значение свойства DataBaseName равным larek, ничего хорошего из этого у вас не получится, и внятно объяснить, что и как происходит, Delphi не сможет. Но как только вы создадите свой ма-а-аленький локальный larek на выходе компонента TDataBase, мир вновь заиграет всеми красками радуги – использование TDataBase приведет к формированию следующего потока данных в приложении, написанном на Delphi (рис.6).
Еще три свойства, которые играют важную роль при разработке приложений, – Connected, KeepConnection и Params. Рассмотрим их по порядку.
Свойство Connected (тип boolean) определяет, подключены вы к серверу или нет. Как только вы попытаетесь на этапе проектирования перевернуть этот флажок в true, Delphi начинает процесс установки соединении (login). После установки соединения вы сможете уже на этапе проектирования проверять корректность настройки таблиц и элементов управления базами данных (раздел Database Control) на "живых" данных.
Следующее свойство KeepConnection предназначено для поддержания соединения с сервером даже в том случае, если в данный момент времени нет ни одной открытой таблицы или выполняемого запроса. За это приходится платить некоторым расходом ресурсов сервера, но зато достигается существенное повышение производительности системы "клиент-сервер" в целом.
И наконец, свойство Params позволяет модифицировать настройки ВОЕ при установлении соединения с базой данных как на этапе проектирования, так и во время выполнения программы. Вы можете, например, изменить имя и пароль пользователя, открывающего базу данных, указать, какое из средств языковой поддержки вы хотите использовать для этой базы, определить стратегию использования кэша сервера и т.д. Таким образом, TDataBase обеспечивает гибкую, но неразрывную связь между клиентской частью программного комплекса, разрабатываемого вами на Delphi, и удаленным сервером, которым вы управляете с помощью SQL-запросов.
Формирование и обработка SQL-запросов
Доступ к SQL осуществляется через компонент TQuery. К сожалению, до сих пор ошибочно полагают, что TQuery предназначен исключительно для получения выборок из баз данных в соответствии с тем или иным условием запроса. На самом деле с помощью этого компонента можно выполнить любой SQL-запрос, соответствующий стандарту ANSI SQL 6, то есть не зависящий от особенностей используемого сервера. Доступ к частным расширениям SQL можно получить, вызывая внутренние процедуры сервера из вашего приложения.
Чтобы в полной мере пользоваться возможностями TQuery, необходимо уяснить, что этот компонент поддерживает два различных механизма формирования запроса к серверам.

Метод Query.Open предназначен для обработки запросов, которые возвращают курсор (указатель на запись) или устроены так, что в результате их выполнения сервер формирует некоторое выходное множество данных и возвращает их клиенту. Вот пример запроса, который посылается посредством метода Open:
SELECT * FROM AUTHOR WHERE NAME – 'АФАНАСИЙ НИКИТИН'
Метод Query.execSQLnpeflHa3H34eH для выполнения запросов, которые не возвращают ни курсор, ни наборы данных. Вот характерный пример использования этого метода:
QUERY 1 .SQL. ADD(INSERT INTO AUTHOR (ID, NAME) VALUES (3454,' КНЯЖНА ТАРАКАНОВА'));
QUERY1.EXECSQL;
Этот метод позволяет не только манипулировать данными, но и определять собственные таблицы, индексы и так далее.
Все большую популярность получают встроенные процедуры и триггеры. Без их использования даже могучий Ingress или Informix превращаются в подобие FoxPro! Встроенные процедуры компилируются только один раз, при загрузке системы, и хранятся на сервере (точнее, среди метаданных вашей базы) уже в скомпилированном виде. К числу подобных средств относятся, например, параметрические запросы. Значения переменных связи в них могут быть переданы запросу на этапе его выполнения.
Формирование параметрического запроса осуществляется, как это было принято в Советской Армии, по разделениям ("Делай Раз! Делай Два!"). Итак, давайте рассмотрим основные "разы" процесса подготовки и выполнения параметрического запроса.
Определение SQL-запроса (запрос сохраняется в объекте Tstrings, являющемся составной частью компонента TQuery):
queryt.sql. clear;
{ очистка текстового буфера запроса }
query! .sql.add(update author set Name = 'Бумбараш' where ID = :ID_NO);
Подготовка запроса (запрос отправляется на SQL-сервер, компилируется, и сервер подготавливает план выполнения запроса):
QUERY1.PREPARE;
Подстановка параметров (приложение передает параметры в массив Params):
QUERY I.PARAMSfO] .
ASINTEGER = 343;
ИЛИ QUERY1 .PARAMBYNAME-
(ID_NO).ASINTEGER := 343;
Выполнение (запрос вместе с параметрами отправляется на сервер):
QUERY1.EXECSQL;
Точно так же вы можете создать некоторое подобие программы ISQL, которая обрабатывает вводимые вами запросы и сразу их выполняет. За подробностями позвольте отослать вас к литературе [3], где вы сможете найти и примеры использования механизма обработки ошибок при выполнении транзакций.
Работа со встроенными процедурами
Несмотря на то, что выполнить обращение к встроенным процедурам можно посредством компонента TQuery, в Delphi реализован специальный компонент TStoredProc, который предоставляет в ваше распоряжение удобный инструмент визуального управления вызовом встроенных процедур. Подобно запросам, встроенные процедуры могут возвращать выходные наборы данных, которые могут использоваться в прикладной программе.
Отметим, что различные серверы баз данных поддерживают различные методы и форматы возвращаемых данных, но Delphi дает программисту возможность без особых проблем получить доступ к информации. Например, встроенные процедуры InterBase, возвращающие набор данных (то есть процедуры, аналогичные команде SQL select), могут быть вызваны с использованием самой команды select, аргументом которой является не таблица базы данных, а процедура.
Вот процедура, которая вернет фотокарточку нашего автора (или логотип фирмы):
create procedure GetPhoto retuns (photoret BLOB) as begin
for select photo from author into :photoret /* собственно выборка */ do
begin
suspend;
/* ожидаем пока будет считан результат */
end
end
Эта процедура, которую вы можете ввести с помощью ISQL, выполняется посредством вызова:
TQUERY: SELECT * FROM GETPHOTO
Но этот метод работает только с InterBase. Для всех прочих серверов вам придется использовать компонент TStoredProc. Как только вы осуществите подключение к серверу (TDataBase.Connec-ted=true), свойство компонента StoredProcName может быть установлено на любую из процедур, определенных в подключенной базе данных. После этого вы можете просмотреть список параметров процедуры, и хотя в него нельзя вносить никакие исправления, поскольку само определение процедуры недоступно, можно установить значения параметров, принимаемые по умолчанию.
Этапы выполнения встроенной процедуры во многом совпадают с обслуживанием параметрического запроса. Для примера предположим, что мы создали встроенную процедуру, которая подсчитывает количество авторов по каждому из населенных пунктов (который определяется значением почтового индекса). Программа использует входной параметр "индекс населенного пункта", а выходным является количество авторов в этом городе/пгт/деревне. Тогда основные шаги программы можно сформулировать следующим образом.
Определение встроенной процедуры (устанавливаем свойство Name):
STOREDPROC1.NAME : = 'GETAUTHORCOUNT';
Подготовка процедуры (задаем параметры вызова по умолчанию из свойства Params):
STOREDPROC1 .PREPARE;
Подстановка текущих значений параметров (обновление массива Params):
STOREDPROC1.PARAMS[0] : = 252000;
Выполнение процедуры (параметры передаются на сервер для выполнения):
STOREDPROC1 .EXECPROC;
Результат выполнения процедуры возвращается в массив Params и считывается оттуда программой пользователя:
LABELS.CAPTION : =
STOREDPROC1 .PARAMS11 |.ASSTRING;
Немного о репликации данных
Задача репликации (в моем понимании) фактически сводится к организации передачи данных от сервера к клиенту и обратно. Видимо, точно так же считает и коллектив разработчиков Delphi. Во всяком случае, только этим можно объяснить появление в группе DataBase Access компонента TBatchMove, который обеспечивает удобный интерфейс для обмена данными между любыми базами данных, поддерживаемыми BDE. В частности, этот компонент позволяет вам организовать перекачку данных с удаленного сервера на локальную машину или осуществить загрузку удаленной базы подготовленной вами информацией.
Работа с этим компонентом никаких сложностей не вызывает, поскольку назначение его свойств до-вольно прозрачно: Source и Destination устанавливаются соответственно на источник и приемник данных, в роли которых могут выступать компоненты (Ttable, TQuery или TStoredProc). Mode означает тип выполняемой операции. При копировании таблиц будет копироваться и их структура – вместе с типами данных, которые отображаются на соответствующие им типы в БД-приемнике.
Кроме этого, имеется несколько свойств, определяющих таблицы, в которые записываются бракованные данные (например, данные с некорректной установкой ключевого поля). Это позволяет использовать TBatchMove для еще одной важной задачи, которая до сих пор не имеет общего решения – для контроля целостности баз данных.
Свойства TBatchMove, как и всех других компонентов Delphi, могут быть установлены и во время выполнения программы, что позволяет без особых усилий разработать утилиты копирования и переноса данных между базами данных различной структуры. Как только пользователь установит в разработанном вами красивом диалоговом окне все необходимые параметры, а затем нажмет большую кнопку, программа активизирует написанную вами процедуру:
procedure Forml .OnButtonl Click(Sender :Tobject);
begin
BatchMovel .Execute;
end;
Два слова о доступе к гетерогенным базам данных
Разные разработчики как инструментальных, так и прикладных систем ориентировались на разные форматы и способы представления информации, в результате чего появилось огромное количество несовместимых между собой баз данных. Лишь сегодня информационное сообщество наконец-то осознало необходимость интеграции всего этого многообразия.
На практике все мы довольно часто сталкиваемся с необходимостью объединить в единую систему данные, подготовленные в разное время (это к вопросу о версиях) и с помощью разных средств. Delphi поддерживает связанные курсоры в гетерогенных базах, что позволяет вам сформировать запись из таблиц, которые могут находиться даже на разных концах земного шара. Для этого вам достаточно подключить компоненты ТТаЫе или TQuery к соответствующим серверам и просто-напросто связать две таблицы.
Кроме того, вы можете объединить данные, полученные от различных серверов, в одно SQL-выражение. Хотя для этого потребуется предпринять определенные усилия.
Поскольку запрос TQuery позволяет указать в свойстве DataBaseName имя только одного источника данных, вы должны создать синоним, указывающий на ваш локальный или сетевой диск (обычно доступ к удаленным UNIX-машинам осуществляется с помощью NFS, отображающего удаленные диски UNIX-машин на логические виртуальные диски DOS/Windows). Затем создайте синонимы для каждой из баз данных, доступ к которым вы хотите получить. В генерируемом программой SQL-выражении каждая ссылка на таблицу должна сопровождаться указанием синонима соответствующего сервера. Синтаксис SQL-выражения должен соответствовать локальному SQL.
И напоследок я скажу...
Разумеется, обо всех особенностях Delphi, связанных с программированием задач "клиент-сервер", в одной статье рассказать просто невозможно. К тому же, говоря откровенно, многого я и сам еще не знаю. Но одно уяснил твердо – с Delphi мне по пути. Чего и вам желаю!
Литература
[1 ] Xavier Pacheco. Upsizing Delphi Applications to Client Server.// Proceedings of 6th Borland Developers Confeerence, 1995.
[2] Mike Destein. Database Connectivity from Delphi.// Proceedings of 6th Borland Developers Conference, 1995.
[3] Lance C. Bullock. Using Server-Specific Features in Delphi.// Proceedings of 6th Borland Developers Conference, 1995.
{НАЧАЛО ВРЕЗКИ}
• Delphi исключительно непритязательна к вычислительным ресурсам и позволяет разрабатывать и эксплуатировать созданные программы на обычной для нашей страны машине с процессором 8О386 и объемом оперативной памяти 4 Мбайт.
• Delphi удобна. Вам не нужно вспоминать, какую константу – cm_First или wm_First – нужно поместить в описании обработки событий. Да и самих обработчиков приходится писать заметно меньше – большинство тонкостей реализации скрыто в библиотеке компонентов.
• Если большинство продуктов визуального программирования на самом деле являются интерпретаторами (а "пристегиваемые" DLL-модули типа VBRUM 100.DLL – run-time-версиями этих систем), то Delphi предлагает качественно иной подход. Вы получаете на выходе "чистый" ЕХЕ-мо-дуль, который можете передавать пользователям. Правда, в комплект поставки приходится включать VBX-модуль и интерфейсную библиотеку BIVBX11 .DLL.
• В Delphi реализована долгожданная интегрированная среда отладки Windows-программ. Но это лучше один раз увидеть, чем сто раз услышать.
• Вы можете неограниченно расширять набор компонентов, из которых строятся Delphi-приложения. И даже можете превратить это занятие в источник дохода.
{КОНЕЦ ВРЕЗКИ}