Создание приложений с применением COM+. Часть 1

Наталия Елманова

Настоящая статья посвящена созданию и применению COM-серверов, используемых совместно с Microsoft Component Services и иногда называемых в отечественной литературе службами компонентов (расширение COM, которое позволяет создавать приложения, использующие Component Services, получило название COM+). Применение подобных серверов позволяет решить некоторые задачи, не предусмотренные спецификацией COM, таких как авторизованный доступ к COM-серверам и организация распределенных транзакций, а также в ряде случаев снизить требования к ресурсам, необходимым для эксплуатации COM-серверов.

Зачем нужны службы компонентов

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

Еще одна проблема, нередко возникающая в процессе практической реализации проектов, содержащих COM-серверы, состоит в том, что реально создаваемые приложения, в отличие от учебных и книжных примеров, могут иметь довольно сложную архитектуру и, в частности, содержать несколько различных сервисов промежуточного слоя для решения разных задач. Например, некоторые из них могут отвечать за предоставление доступа к данным из нескольких различных СУБД (особенно это актуально в компаниях, когда-то подвергшихся так называемой островковой автоматизации), и при этом может возникнуть потребность в инструменте,  реализующем комплексную функциональность, в частности осуществление распределенных транзакций, затрагивающих базы данных, которые обслуживаются  разными серверными СУБД (например, Microsoft SQL Server и Oracle).

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

Таким образом, существует потребность в расширении COM-технологии посредством службы, обеспечивающей создание COM-объектов для совместного использования многими клиентами, авторизованный доступ к этим объектам, а при необходимости также обработку транзакций этими объектами.  Расширенная таким образом технология COM стала именоваться COM+, а сам сервис, реализующий это расширение и являющийся составной частью Windows 2000, получил название Microsoft Component Services (службы компонентов).

Следует напомнить, что предыдущая версия реализации этого сервиса входит в состав Windows NT4 Option Pack —  свободно распространяемый пакет обновления для Windows NT 4.0 и называется Microsoft Transaction Server 2.0 (MTS).

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

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

Как работают службы компонентов

Рассмотрим типичный сценарий работы приложения, использующего службы компонентов. Пользователь запускает клиентское приложение, реализованное в виде исполняемого файла или Web-приложения. Клиентское приложение пытается установить соединение с объектом COM+. Если компонент COM+, содержащий такой объект, реализован  в виде внутрипроцессного сервера, этот объект   может выполняться в адресном пространстве приложения dllhost.exe или в адресном пространстве клиентского приложения. (Если речь идет о Windows NT и Microsoft Transaction Server, то компонент, содержащий такой объект,  обязательно должен  быть реализован во внутрипроцессном сервере, а  выполняется он в адресном пространстве приложения mtx.exe; объект MTS также может выполняться и в адресном пространстве клиентского приложения.)  В общем случае в объекте  COM+  может быть реализована практически любая функциональность, например такой объект вполне может быть сервером доступа к данным.

Если запрос на установку соединения корректен, то в адресном пространстве того приложения, в котором должен функционировать объект,  создается так называемый контекст объекта (если при этом приложение dllhost.exe не запущено, происходит его запуск).  Контекст объекта содержит такие дополнительные сведения, которые не предусмотрены спецификацией COM и потому не содержатся в COM-объектах, — это  правила доступа к объекту и способ участия его  в транзакциях. Контекст создается для каждого клиента, обслуживает именно его (в отличие от собственно COM-объекта) и «по поручению» клиента взаимодействует с COM-объектом, который может быть и только что созданным, и взятым из имеющегося пула объектов.

После создания контекста клиентское приложение может посылать объекту COM+  запросы на выполнение его методов. Эти методы могут, например, выполнять запросы к  СУБД или реализовывать какие-то иные действия, в том числе обращаться  к другому объекту  COM+ (в последнем случае мы можем говорить о «родительских» и «дочерних» объектах).

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

Если же метод  объекта COM+  успешно завершил свою работу (неважно, с помощью дочерних объектов или без таковой), он информирует об этом  службы компонентов, равно как и о том, что данный клиент в нем больше не нуждается.  Затем данный объект может быть разрушен, возвращен в пул или использован другим клиентом.

После этого краткого введения рассмотрим подробнее особенности функционирования объектов COM+.

Организация пулов объектов и ресурсов

В общем случае словосочетания «организация пула ресурсов» и «организация пула объектов» означают, что в приложении создается некоторое количество ресурсов определенного типа (например, соединений с базой данных) и что если клиенту данного приложения потребуется такой ресурс (или соответственно объект), то он не создается,  а берется из пула и по окончании работы возвращается в пул, а не уничтожается.

Зачем нужна организация пулов объектов? Нередко бывает, что  серверные объекты при своем создании потребляют немалый объем иных ресурсов,  создавая, в частности, большие временные файлы или  сетевой трафик и т.д. Исходя из изложенных выше соображений, такие типы объектов должны быть созданы однократно в заранее определенном количестве с целью последующего их использования клиентскими приложениями. Если в какой-то момент число клиентов, которым нужен такой серверный объект, превысит количество имеющихся экземпляров данных объектов, то следует создать дополнительные их экземпляры. Уничтожение дополнительных экземпляров объектов, которые стали ненужными, требуется производить в соответствии с заранее установленным для них максимальным временем существования в неактивном состоянии.

Реализация  пулов ресурсов в COM+ осуществляется с помощью специальных объектов, которые кэшируют ресурсы для того, чтобы организовать их коллективное использование несколькими объектами COM+, объединенными в приложение. Эти объекты носят названия  resource dispensers (dispenser означает раздаточное устройство или распределитель; назначение называемых этим словом объектов — кэширование общих данных, не подверженных изменению с помощью транзакций) и resource managers (менеждеры ресурсов; они используются при управлении транзакциями и позволяют осуществить такие стандартные требования к ним, как изоляция и атомарность транзакций).  

При создании приложений COM+  довольно часто применяется организация пула соединений с базами данных, в частности пула соединений с  ODBC- и OLE DB-источниками.  При применении такого пула снижается  сетевой трафик между службами компонентов и сервером баз данных посредством снижения частоты установки и разрыва соединений с сервером. Организация пулов поддерживается драйверами, удовлетворяющими спецификациям  ODBC 3.0 и ODBC 3.5 и некоторыми OLE DB-провайдерами, в частности OLE DB Provider for Microsoft SQL Server.

Управление транзакциями

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

Для управления распределенными транзакциями используется специальный сервис — Microsoft Distributed Transaction Coordinator (MS DTC), который может быть активизирован из приложения Component Services Explorer (средства администрирования объектов COM+, доступного в разделе Administrative Tools панели управления Windows 2000), либо из приложения Service Control Manager панели управления Windows 2000, либо из приложения Enterprise Manager, входящего в комплект поставки Microsoft SQL Server 7.0 или 2000 (если таковой имеется на компьютере). MS DTC представляет собой службу операционной системы, которая  координирует транзакции, использующие различные менеджеры ресурсов, и позволяет  реализовать действия, затрагивающие различные базы данных (эти базы могут быть произведены разными производителями и находиться на разных компьютерах)  в рамках единой транзакции.  

Как в COM+  реализованы распределенные транзакции? Когда какой-либо объект COM+  запрашивает доступ к базе данных, то драйвер соответствующей СУБД проверяет в контексте этого объекта, требуется ли ему транзакция, и, если требуется, информирует об этом DTC. Затем драйвер обращается к менеджеру ресурсов, отвечающему за базу данных, с запросом о начале транзакции в ней. И только  теперь компонент может начать манипулировать данными с помощью этого драйвера.

Если нужно изменить данные в двух базах данных с помощью единой транзакции, то DTC  и первый из драйверов инициируют транзакцию в первой базе данных так, как это было описано выше.  При открытии соединения со второй базой данных, DTC и второй драйвер обращаются к соответствующему менеджеру ресурсов с запросом о начале новой транзакции для изменений, производимых во второй базе данных.  В результате  обе базы данных содержат открытые транзакции.

Когда объект COM+, ответственный за какую-либо часть такой транзакции, завершает свою работу,  он вызывает либо собственный метод SetComplete, если выполняемые им операции завершились успешно, либо метод SetAbort, если произошел откат транзакции. Далее DTC обращается к обоим менеджерам ресурсов с запросом на завершение или откат обеих транзакций. Таким образом, изменения в обеих базах данных будут либо вместе сохранены, либо вместе отменены (рис. 1).

Рис. 1. Реализация распределенной транзакции с помощью DTC

Если объект COM+  должен предотвратить завершение  или откат распределеной транзакции, он может вызвать собственный метод DisableCommit. Метод EnableCommit позволяет DTC завершить все открытые несохраненные транзакции.

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

Следует отметить, что части распределенной транзакции обычно реализуются в различных объектах COM+. Ранее мы уже отмечали, что одни объекты, называемые родительскими, могут инициировать создание других объектов, называемых дочерними. Если такой дочерний объект генерирует исключение в процессе манипуляции с базой данных, службы компонентов будут проинформированы о необходимости отката транзакции. В этом случае DTC инициирует откат транзакции родительского объекта и уведомляет об этом клиентское приложение.  Поэтому для реализации подобного управления транзакциями следует поместить код, реализующий различные части распределенной транзакции, в разные объекты  COM+, а затем создать для них родительский объект. При использовании такого механизма контроля транзакций клиентское приложение может не содержать ни кода, связанного с завершением или с откатом транзакций, ни кода, отвечающего за соединение с базой данных.

Вопросы безопасности

Службы компонентов  позволяют использовать список пользователей и групп пользователей Windows 2000 в качестве списка пользователей своих объектов. При этом  для каждого объекта можно установить правила его эксплуатации различными пользователями и группами пользователей. Помимо этого  cлужбы компонентов  поддерживают также механизм ролей. Роль в службах компонентов —  это совокупность пользователей и групп пользователей, которые имеют право обращаться к интерфейсам объектов, включенных в данное приложение COM+. При использовании ролей можно не включать код реализации правил безопасности в объекты COM+.

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

Рассмотрев возможности служб компонентов, перейдем к самим объектам COM+, а именно к тому, каким требованиям они должны удовлетворять и как они регистрируются.

Особенности объектов COM+

Требования к объектам COM+

Объекты COM+ являются серверами автоматизации и, как и другие подобные серверы, реализуют интерфейс IDispatch. Все объекты COM+ поддерживают специфический для них интерфейс IObjectControl, содержащий методы для активации и деактивации объекта COM+ и управления ресурсами, в том числе соединениями с базами данных.

Серверный объект COM+ должен иметь стандартную фабрику классов и библиотеку типов (и та и другая автоматически создаются при использовании эксперта Transactional Object wizard). Помимо этого компонент должен, как и все внутрипроцессные серверы автоматизации, экспортировать функцию DllRegisterServer  и осуществлять автоматическую регистрацию его CLSID (эксперт Transactional Object wizard автоматически генерирует соответствующий код).

Если предполагается коллективное использование серверного объекта, он должен не хранить внутри себя сведения о состоянии данных, связанных с конкретным клиентом (например, результаты запросов, номера текущей записи в наборе данных и т.д.), а немедленно пересылать такие сведения клиентскому приложению. Объекты, удовлетворяющие этому требованию, обозначаются термином «не хранящие состояние»  (stateless objects).

В общем случае при создании кода объектов COM+  рекомендуется пользоваться соединением с базой данных минимальное время и как можно быстрее вернуть его в соответствующий пул ресурсов. Ссылка же на контекст объекта при этом может сохраняться достаточно долго. По этой же причине рекомендуется также вызывать метод SetComplete как можно чаще, чтобы как можно быстрее вернуть объект в соответствующий пул объектов.

Особенности управления объектами COM+

Объекты COM+ объединяются в приложения COM+ — Applications (в MTS они назывались; «пакетами» — packages), каждый из которых может содержать один или несколько объектов. Управление объектами и приложениями COM+ осуществляется с помощью приложения Componet Services Explorer, которое доступно в разделе Administrative Tools панели управления Windows 2000.

Каждый объект, зарегистрированный как объект COM+, обладает свойством Transaction Support, определяющим правила участия объекта в транзакциях. Это свойство принимает пять возможных значений:

Disabled — для данного объекта не создается контекст транзакции;

Not Supported — службы Component Services не запускают компонент внутри контекста транзакции;

Supported — службы Component Services запускают компонент внутри контекста транзакции, если таковая запрошена, в противном случае — вне контекста;

Required — службы Component Services помещают объект в контекст транзакции при вызове любого из методов. Если для этого объекта транзакция недоступна, инициируется новая;

Requires new — службы Component Services инициирует новую транзакцию при создании объекта независимо от того, какие транзакции в этот момент выполняются.

Говоря о COM+, нужно отметить механизм обработки событий, возникающих в таких объектах. В отличие от событий COM, где за уведомлением клиентов следит сам сервер, в COM+ за событиями и уведомлением следят службы компонентов. При этом для генерации событий создается отдельный объект-издатель, к которому производится обращение в момент наступления события. Затем службы компонентов обращаются ко всем клиентам, подписанным на это событие (они содержат объекты-подписчики).

Объекты-издатели не должны содержать реализации своих интерфейсов — она содержится не в серверном объекте, а в клиентском приложении (в подписчике на данное событие). Поэтому после описания интерфейсов компонент компилируется и регистрируется в службах компонентов. При наступлении события в объекте COM+ следует инициировать создание объекта-издателя и вызвать метод, соответствующий данному событию. В объекте-подписчике (это обычный COM-объект), как правило, реализуется интерфейс объекта-издателя, а также методы этого интерфейса (в данном случае реализация методов), которые играют роль обработчиков событий, генерируемых объектом-издателем. Отметим, что и объект-издатель, и объект-подписчик должны быть зарегистрированы в одном и том же приложении COM+.

Рассмотрев особенности управления объектами COM+, выясним, как создаются такие объекты. В качестве средства разработки мы будем использовать Delphi 6 и начнем мы с классов Delphi, с помощью которых можно создать такие объекты.

Классы Delphi для создания объектов

В Delphi 6 Enterprise имеются два класса для создания приложений COM+ — TMtsAutoObject и TMtsDataModule. Оба класса предназначены для создания серверов автоматизации, реализующих интерфейс IObjectControl (строго говоря, реализация этого интерфейса обязательна только для объектов MTS, но не обязательна для объектов COM+, хотя и в последнем случае она рекомендуется). В отличие от класса TMtsAutoObject, класс TMtsDataModule представляет собой модуль данных, в который можно помещать невизуальные компоненты. Он реализует интерфейс IAppServer, позволяющий применять в приложениях COM+ компоненты DataSnap.

Оба эти класса реализуют ряд методов, характерных для объектов COM+.

Метод Описание
SetComplete Вызывается  после того, как объект COM+ успешно выполнил свою работу и больше не нуждается в данных, связанных с обслуживаемым им клиентом. После вызова этого метода объект становится неактивным. Если объект выполняется в контексте транзакции, вызов метода SetComplete означает, что эта часть транзакции готова к завершению
SetAbort Вызывается в том случае, когда возникает необходимость отката транзакции, в которой участвует данный объект. Если транзакция была запущена автоматически службами Component Services специально для этого объекта, она будет прекращена, а объект будет уничтожен. Если же объект был частью «чужой» транзакции,  она не будет завершена. Обычно этот метод вызывается при обработке исключений в блоке except предложения try..except
EnableCommit Вызывается, если объект COM+  может завершить свою транзакцию, не избавляясь от данных, связанных с клиентом. Метод не инициирует уничтожения объекта. Его вызов означает, что транзакцию с участием этого объекта можно завершить
DisableCommit Вызывается в случае, если объект не может завершить текущую транзакцию до тех пор, пока не будет вызван EnableCommit или SetComplete
IsInTransaction С помощью этого метода можно определить, выполняется ли данный объект внутри транзакции
IsCallerInRole Этот метод обладает входным параметром типа  WideString. С его помощью можно проверить, соответствует ли клиент роли, имя которой указано во входном  параметре
IsSecurityEnabled Данный метод позволяет проверить, защищен ли объект службами безопасности COM+

Создание серверных объектов

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

Мы начнем с создания, во-первых, простейшего сервера доступа к данным, который позволяет манипулировать данными таблицы  Products базы данных Northwind, входящей в комплект поставки Microsoft SQL Server, а во-вторых, клиентского приложения для его тестирования. Затем мы создадим второй объект COM+ для манипуляции данными в таблице, хранящей сведения о заказанных товарах (ее мы создадим в этой же базе данных). Далее мы создадим объект, выполняющий транзакцию, которая затрагивает обе таблицы, и активизирующий оба ранее созданных объекта внутри своей транзакции. И наконец, мы перенесем таблицу Products из базы данных Northwind в базу данных Oracle 8 и внесем соответствующие изменения в компоненты доступа к данным нашего первого объекта COM+, чтобы рассмотреть, как распределенные транзакции управляются с помощью  Distributed Transaction Coordinator.

Клиентские приложения и объекты COM+ могут располагаться как на одном и том же компьютере, так и на разных компьютерах одного домена (в этом случае мы можем использовать доступ с помощью DCOM). Компьютер, на котором расположены объекты COM+ (далее мы будем называть его серверным компьютером), должен иметь доступ к базам данных, к которым они будут обращаться.

Для доступа к данным обеих СУБД мы будем использовать ADO, а результаты запросов в клиентское приложение будем передавать в виде наборов данных ADO Recordset. Если примеры выполняются не в Windows 2000 (и, следовательно, используется не Component Services, а MTS), нужно удостовериться, что на клиентском и серверном компьютерах установлены библиотеки MDAC (Microsoft Data Access Components) и служба DCOM и что на серверном компьютере установлен MTS (напомним, что он входит в состав  Windows NT Option Pack, а последний доступен на Web-сервере корпорации Microsoft). Перед тестированием компонентов следует также запустить Distributed Transaction Coordinator.

В нашем первом примере мы покажем, как создать простейший объект COM+, а затем создадим простейшее клиентское приложение для его тестирования.

Создание объекта COM+ для доступа к данным

Первый объект COM+, который мы создадим, будет манипулировать данными в таблице Products базы данных Northwind из комплекта поставки Microsoft SQL Serve 7.0 или 2000. В нем мы реализуем методы для уменьшения значения поля UnitsInStock и увеличения значения поля  UnitsOnOrder данной таблицы (эти операции производятся, когда со склада заказывают товар), а также для отправки результатов в клиентское приложение с целью контроля результатов этой манипуляции данными.

Наше первое приложение будет состоять из следующих частей, изображенных на рис. 2.

Рис. 2. Простейшее приложение COM+

Создание объекта COM+ мы начнем с нового проекта библиотеки ActiveX, вызвав пункт меню File | New | Other и выбрав значок ActriveX Library со страницы ActiveX окна репозитария объектов. Сохраним проект под именем STOCK. Далее на странице Multitier окна репозитария объектов выберем значок Transactional Data Module и заполним диалоговую панель соответствующего эксперта. Как и в случае обычного COM-объекта, нам следует ввести имя COM-класса (назовем его Stock_Data) и выбрать модель потоков (обычно в объектах COM+ применяется модель Apartment). Кроме того, следует выбрать значение свойства Transaction Support создаваемого объекта (в данном эксперте она носит название Transaction model). Сейчас мы выберем значение Requires a transaction, что позволит в дальнейшем использовать данный объект в качестве участника транзакции, инициированной другим объектом. После этого будут сгенерированы модуль данных (потомок класса TMtsDataModule) и соответствующая библиотека типов.

Теперь поместим в созданный модуль данных компоненты TADOConnection и TADOCommand. Свойство Connection компонента ADOCommand1 установим равным ADOConnection1, а свойство ConnectionString компонента ADOConnection1 установим равным:

Provider=SQLOLEDB.1;Password="";  
Persist Security Info=True;User ID=sa;  
Initial Catalog=Northwind;Data Source=MAINDESK  

Следует иметь в виду, что имя сервера, имя пользователя и пароль на вашем компьютере могут быть другими. Нужно также установить свойство  LoginPrompt компонента  ADOConnection1 равным False, чтобы избежать появления диалога аутентификации пользователя на серверном компьютере при попытке активации  создаваемого объекта.

Теперь откроем библиотеку типов созданного объекта и создадим в ней три новых метода. Первый из них, Dec_UnitsInStock, будет уменьшать значение поля  UnitsInStock одной из записей таблицы Products, второй, Inc_UnitsOnOrder, будет увеличивать значение поля  UnitsOnOrder той же записи. Оба эти метода должны иметь два целых входных параметра — ProductID, равный значению первичного ключа, идентифицирующего запись, и OrdQuantity, равный количеству единиц заказанного товара. Третий метод (назовем его Get_ProductList) будет возвращать набор данных (ADO Recordset) с результатом запроса к таблице Products в клиентское приложение. Чтобы иметь возможность использовать такой тип данных, сошлемся на библиотеку Microsoft ActiveX DataObjects Recordset 2.6 Library в библиотеке типов объекта stock (это можно сделать, выбрав пункт контекстного меню Show All Type Libraries закладки Uses в редакторе библиотеки типов и отметить указанную выше библиотеку в появившемся списке). Для создания метода Get_ProductList определим свойство ProductList типа Recordset, доступное только для чтения.

Результат редактирования библиотеки типов представлен на рис. 3.

Рис. 3. Библиотека типов объекта Stock_Data

Описав методы объекта COM+, нажмем кнопку Refresh Implementation в редакторе библиотек типов. Теперь нам следует реализовать эти методы. Первый из них, как было сказано ранее, уменьшает значение поля UnitsInStock. Возможные значения этого поля должны быть больше или равны нулю (таково ограничение, присутствующее в базе данных и отражающее очевидное бизнес-правило: число единиц товара на складе не должно быть отрицательным). Следовательно, этот метод является потенциальным источником исключения, связанного с нарушением серверного ограничения. Поэтому мы учтем этот факт в коде и обработаем исключение согласно правилам, принятым в COM+ и рассмотренным выше. Таким образом, реализация данного метода имеет вид:

procedure TStock_Data.Dec_UnitsInStock(ProductID,   
  OrdQuantity: Integer);  
begin  
  try  
  //Соединяемся с базой данных  
  ADOConnection1.Open;  
  //Текст запроса к базе данных  
  ADOCommand1.CommandText :=   
  'UPDATE Products SET UnitsInStock=UnitsInStock-'  
  +IntToStr(OrdQuantity)+' WHERE ProductID='   
  +IntToStr(ProductID);  
  //Выполняем запрос  
  ADOCommand1.Execute;  
  //Разрываем соединение с базой данных  
  ADOConnection1.Close;  
  //Информируем Component Services об успешном  
  //выполнении запроса  
  SetComplete;  
 except  
  ADOConnection1.Close;  
  //Информируем Component Services о неудачной попытке  
  //выполнения запроса  
  SetAbort;  
  //Передаем исключение вызывающему приложению  
  raise;  
 end;  
end;  

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

procedure TStock_Data.Inc_UnitsOnOrder(ProductID,   
  OrdQuantity: Integer);  
begin  
 try  
 //Соединяемся с базой данных  
 ADOConnection1.Open;  
  //Текст запроса к базе данных  
  ADOCommand1.CommandText :=   
  'UPDATE Products SET UnitsOnOrder=UnitsOnOrder+'  
  +IntToStr(OrdQuantity)+' WHERE ProductID='   
   +IntToStr(ProductID);  
  //Выполняем запрос  
  ADOCommand1.Execute;  
  //Разрываем соединение с базой данных  
  ADOConnection1.Close;  
  //Информируем Component Services об успешном  
  //выполнении запроса  
  SetComplete;  
 except  
  //Разрываем соединение с базой данных  
  ADOConnection1.Close;  
  //Информируем Component Services о неудачной попытке  
  //выполнения запроса  
  SetAbort;  
  //Передаем исключение вызывающему приложению  
  raise;  
 end;  
end;  

Третий метод обращается с запросом к таблице Products для получения ее содержимого в виде ADO Recordset. Его реализация такова:

function TStock_Data.Get_ProductList: Recordset;  
 var QRY: string;  
begin  
 QRY := 'SELECT ProductID, ProductName, UnitPrice, '+   
   'UnitsInStock, UnitsOnOrder from Products '+  
   'WHERE Discontinued=0';  
 try  
 //Создаем набор данных  
  Result := CoRecordset.Create;  
  Result.CursorLocation := adUseClient;  
 //Открываем его  
 Result.Open(QRY,ADOConnection1.ConnectionString,  
  adOpenStatic,adLockBatchOptimistic, adCmdText);  
  //Разрываем соединение с базой данных  
  ADOCOnnection1.Close;  
  //Информируем Component Services об успешном  
  //выполнении запроса  
 SetComplete;  
 except  
  //Разрываем соединение с базой данных  
  ADOConnection1.Close;  
  //Информируем Component Services о неудачной попытке  
  //выполнения запроса  
  SetAbort;  
  //Передаем исключение вызывающему приложению  
  raise;  
 end;  
end;  

Для работосособности данного метода следует сослаться в секции Uses на модули ADODB, ADODb_TLB, ADOR_TLB (последний генерируется при ссылке на библиотеку типов Microsoft ActiveX DataObjects Recordset 2.6 Library).

Теперь нам следует скомпилировать и сохранить проект, а затем зарегистрировать его. Если разработка компонента, содержащего наш объект,  велась на серверном компьютере, нужно выбрать пункт главного меню Delphi Run | Install COM+ Objects (в этом случае необходимо ввести имя нового приложения COM+, в который следует установить этот объект – пусть он называется COMPlus_Demo).

Если же разработка объекта велась не на серверном компьютере, то созданный компонент — файл STOCK.DLL — следует скопировать в любой каталог серверного компьютера и зарегистрировать его с помощью Component Services Explorer. В этом случае нужно создать новое приложение COM+, щелкнув правой клавишей мыши на элементе Computer | COM+ Applications в левой части окна Component Services Explore и затем выбрав из контекстного меню этого элемента пункт New | Application. Назовем наше приложение COMPlus_Demo. Далее в созданное приложение, пока еще ничего не содержащее, добавим созданный компонент. Для этого раскроем список папок созданного приложения, из контекстного меню раздела Components выберем пункт New | Component и в появившихся диалоговых панелях ответим на вопросы, касающиеся устанавливаемого компонента. Можно также просто перетащить имя файла компонента COM+ в Component Services Explorer.

После этого возможно найти и приложение, и сам компонент в Component Services Explorer (рис. 4).

Рис. 4. Компонент Stock_Data, зарегистрированный  в Component Services

Создав простейший компонент COM+ и зарегистрировав его в Component Services, мы можем приступить к созданию клиента.

Тестирование объекта COM+ для доступа к данным

Клиент Component Services будет представлять собой обычное Windows-приложение (в общем случае это может быть и Web-приложение, но мы здесь не будем его рассматривать). Создадим новый проект, поместим на его форму компоненты TRDSConnection и TADODataSet. Свойство ServerName компонента RDSConnection1 установим равным программному идентификатору (ProgID) созданного ранее объекта COM+ (в данном случае stock.Stock_Data), а свойство ComputerName должно быть равно имени серверного компьютера. Как уже говорилось выше, для соединения с удаленным компонентом в этой ситуации используется DCOM. Никаких свойств компонента ADODataSet1 в процессе разработки устанавливать не нужно — он просто получит набор данных на этапе выполнения. Однако нам следует продемонстрировать полученный набор данных пользователю, поэтому поместим на форму компоненты TDataSource, TDBGrid и TDBNavigator и сошлемся в них на компонент ADODataSet1. Наконец, поместим компонент  TEdit для ввода числа единиц заказанного товара и две кнопки, с помощью которых мы будем вызывать методы объекта COM+.

Следующий шаг — создание обработчиков события  OnClick этих кнопок. Первый из них будет получать от объекта COM+ набор данных и отображать их:

procedure TForm1.Button1Click(Sender: TObject);  
begin  
 try  
  //Создаем контекст объекта Stock_Data  
  RDSConnection1.Connected := True;  
  //Получаем набор данных у объекта Stock_Data  
  RS := RDSConnection1.GetRecordset('ProductList','');  
  ADODataSet1.Recordset := RS;  
  //Показываем его пользователю  
  ADODataSet1.Open;  
  //Теперь можно вызывать другие методы этого объекта  
  Button2.Enabled := True;  
 except  
  //Объект не может вернуть данные  
  ShowMessage('Список товаров недоступен');  
 //Тогда мы не можем вызывать его методы  
  Button2.Enabled := False;  
 end;  
 //Освобождаем контекст объекта  
 RDSConnection1.Connected := False;  
end;  

Обработчик события OnClick другой кнопки эмулирует обработку заказа, вызывая два других метода нашего объекта:

procedure TForm1.Button2Click(Sender: TObject);  
var   
 PID, Quantity : Integer;  
begin  
 //Идентифицируем товар по значению поля ProductID  
 PID := ADODataSet1.FieldByName('ProductID').AsInteger;  
 //Определяем, сколько единиц товара мы хотим заказать  
 Quantity := StrToInt(Edit1.Text);  
 try  
  //Создаем контекст объекта Stock_Data  
  RDSConnection1.Connected := True;  
  //Вычитаем значение Quantity из значения  
  //поля UnitsOnOrder   
  RDSConnection1.AppServer.Dec_UnitsInStock(PID, Quantity);  
  //Добавляем Quantity к значению поля  UnitsOnOrder  
  RDSConnection1.AppServer.Inc_UnitsOnOrder(PID, Quantity);  
  Button1Click(self);  
 except  
  //В объекте возникло исключение  
  Showmessage('Заказ не принят');  
 end;  
 //Освобождаем контекст объекта  
 RDSConnection1.Connected := False;  
end;  

Нам следует также описать переменную RS :

var  
  Form1 : TForm1;  
  RS    : _Recordset;  

Теперь мы можем скомпилировать и сохранить клиентское приложение, а затем скопировать его на клиентский компьютер. Запустим клиентское приложение и нажмем первую кнопку. Мы должны получить набор данных, который будет отображен в компоненте TDBGrid (рис. 5). Если при этом открыт и Component Services Explorer, можно наблюдать анимацию значка компонента, к которому происходит обращение. Как только закончится пересылка данных, анимация должна прекратиться, поскольку свидетельствует о том, что объект находится в активном состоянии.

Рис. 5. Клиентское приложение, использующее объект Stock_Data

Теперь можно выбрать одну из записей полученного набора данных, ввести целое число  (например, 1) в компонент Edit1 и нажать вторую кнопку. После этого набор данных должен обновиться, и в выбранной записи значение поля UnitsInStock должно уменьшиться, а значение поля UnitsOnOrder – увеличиться на введенную в компонент Edit1 величину. Однако если мы попытаемся ввести в компонент Edit1 число, превышающее значение UnitsInStock, то объект COM+, выполняя запрошенный метод с этим параметром, инициирует нарушение ограничения на значение этого поля, содержащееся в базе данных, и, следовательно, возникновение исключения в самом объекте COM+. После этого все манипуляции с данными прекратятся, а исключение будет передано вызывающему приложению, которым в данном случае является наше клиентское приложение. В нем-то мы и увидим сообщение об ошибке: «Заказ не принят».

Обратите внимание на то, что сведения о количестве  завершенных и отмененных транзакций можно найти в разделе Distributed Transaction Coordinator | Transaction Statistics приложения Component Services Explorer (рис. 6).

Рис. 6. Сведения о количестве завершенных и отмененных транзакций

Таким образом, вы научились создавать простейшие объекты COM+, регистрировать и тестировать их. Следующим шагом будет реализация транзакции, состоящей из нескольких частей и реализованной в разных объектах. Этому будет посвящена вторая часть данной статьи.

КомпьютерПресс 4'2002


Наш канал на Youtube

1999 1 2 3 4 5 6 7 8 9 10 11 12
2000 1 2 3 4 5 6 7 8 9 10 11 12
2001 1 2 3 4 5 6 7 8 9 10 11 12
2002 1 2 3 4 5 6 7 8 9 10 11 12
2003 1 2 3 4 5 6 7 8 9 10 11 12
2004 1 2 3 4 5 6 7 8 9 10 11 12
2005 1 2 3 4 5 6 7 8 9 10 11 12
2006 1 2 3 4 5 6 7 8 9 10 11 12
2007 1 2 3 4 5 6 7 8 9 10 11 12
2008 1 2 3 4 5 6 7 8 9 10 11 12
2009 1 2 3 4 5 6 7 8 9 10 11 12
2010 1 2 3 4 5 6 7 8 9 10 11 12
2011 1 2 3 4 5 6 7 8 9 10 11 12
2012 1 2 3 4 5 6 7 8 9 10 11 12
2013 1 2 3 4 5 6 7 8 9 10 11 12
Популярные статьи
КомпьютерПресс использует