Author: | Евгений Охотников |
---|---|
Contact: | eao197 at intervale dot ru; eao197 at yahoo dot com |
Date: | 2007.08.26 |
Библиотека MBAPI (Message Box API) появилась как попытка красиво разрешить одну задачу, где намечалось несколько интересных условий. Во-первых, предполагалась рассылка сообщений клиентам некоей системы. Клиент мог настраивать способ получения сообщения: в виде SMS на телефон, в виде письма на e-mail или какой-нибудь еще. Систему предполагалось делать в виде нескольких модулей. Один из них формировал сообщение и выбирал адрес, а другой занимался доставкой по конкретному виду транспорта в зависимости от типа адреса.
Вторая особенность заключалась в том, что генерирующий сообщения модуль должен был быть написан на C++, но без использования SObjectizer. А транспортный модуль должен был быть написан на C++ с использованием SObjectizer.
В таких условиях появилась идея создать транспортную библиотеку MBAPI, которая бы использовала средства SObjectizer-а для построения распределенных приложений (т.е. сообщения глобальных агентов), но при этом:
Для достижения этих целей и была разработана библиотека MBAPI.
С течением времени менялись условия использования MBAPI и набор требований к ней. Первоначальные идеи о поддержке разных типов адресов со временем выродились в начиличие всего двух типов: server_dest_t и client_dest_t. Т.е. MBAPI-компонент, который играл роль сервера, обязательно должен был иметь тип адреса server_dest_t. А MBAPI-компоненты, которые играли роль клиента (т.е. подключались к серверу и инициировали запросы), обязательно должны были иметь тип адреса client_dest_t. Если же оказывалось, что какой-то компонент должен был играть сразу несколько ролей (для кого-то быть клиентом, для кого-то сервером), то он должен был иметь два адреса: один типа client_dest_t, другой типа server_dest_t. Поскольку это затрудняло программирование с использованием MBAPI, то в конце-концов остался только один тип адреса: mbox_dest_t, который является именем т.н. почтового ящика (mbox).
Так же со временем отпала необходимость в использовании MBAPI в не-SObjectizer приложениях. Поэтому со временем из трех взаимоствязанных проектов (собственно mbapi, mbapi_srv для создания серверных SObjectizer-приложений и mbapi_cln для клиентских приложений) в составе MBAPI остался только один проект: mbapi_3. Но, т.к. в течении какого-то времени новая версия MBAPI должна была использоваться вместе с предыдущими, то функциональность по поддержке mbox и mbox_dest_t была добавлена в отдельное пространство имен mbapi_3_mbox. Основные же понятия, унаследованные из предыдущих версий MBAPI находятся в пространстве имен mbapi_3.
В MBAPI сообщение -- это объект производного от mbapi_3::msg_t класса. Класс mbapi_3::msg_t унаследован от oess_1::stdsn::serializable_t. Т.е. в качестве механизма сериализации используется библиотека ObjESSty.
Для создания собственного сообщения необходимо создать класс, производный от mbapi_3::msg_t и описать его схему для ObjESSty. После чего класс может использоваться в качестве типа сообщения в MBAPI:
// Описание сообщения для C++. class demo_msg_t : public mbapi_3::msg_t { OESS_SERIALIZER( demo_msg_t ) public : demo_msg_t(); demo_msg_t( const std::string & name, const std::set< std::string > & supported_modes ); virtual mbapi_3::msg_t * clone() const; private : std::string m_name; std::set< std::string > m_supported_modes; }; || Описание сообщения для ObjESSty. {type demo_msg_t {extensible} {super mbapi_3::msg_t} {attr m_name {of std::string}} {attr m_supported_modes {of {stl-set} std::string}} }
Пунктом назначения является имя MBAPI-компонента, которому адресуется сообщение. Базовым классом для представления типов адресов в MBAPI служит тип mbapi_3::dest_t. Но в настоящее время используется только один его наследник -- тип mbapi_3::mbox_dest_t:
//! Пунктом назначения является mbox. /*! \since v.3.3.0 mbox идентифицируется одним строковым значением. */ class MBAPI_3_TYPE mbox_dest_t : public dest_t { OESS_SERIALIZER_EX( mbox_dest_t, MBAPI_3_TYPE ) public : //! Тип пункта назначения. static id_t< mbox_dest_t > dest_type; //! Конструктор по умолчанию. mbox_dest_t(); //! Инициализирующий конструктор. mbox_dest_t( const std::string & name ); virtual ~mbox_dest_t(); /*! \name Реализация унаследованных методов. \{ */ virtual const identity_t & query_type() const; virtual dest_t * clone() const; virtual std::ostream & dump( std::ostream & o ) const; /*! \} */ //! Получение идентификатора mbox-а. const std::string & name() const; /*! \name Операторы сравнения При сравнении учитывается регистр символов. */ //!\{ //! Строгое равенство. bool operator==( const mbox_dest_t & o ) const; //! Строгое неравенство. bool operator!=( const mbox_dest_t & o ) const; //!\} //! Задан ли идентификатор mbox-а. /*! \return true, если идентификатор mbox-а не пуст. */ operator bool() const; //! Не задан ли идентификатор mbox-а. /*! \return true, если идентификатор mbox-а пуст, т.е. не задан. */ bool operator!() const; protected : //! Вспомогательный метод для оператора сравнения. /*! \return всегда false. */ virtual bool less( const comparable_t & o ) const; private : //! Идентификатор mbox-а. std::string m_name; };
Т.е. mbox_dest_t предназначен для хранения всего одного строкового значения -- имени почтового ящика (mbox-а). Имена mbox-ов выбираются разработчиком конкретной прикладной системы исходя из собственных предпочтений. Каких-либо ограничений на имена mbox-ов не налагается, но используется соглашение об именовании mbox-ов как пространств имен в C++ (например, aag_3::smsc_map::default, aag_3::smpp_smsc::local_test).
С каждым сообщением в MBAPI связано два адреса: to -- адрес получателя и reply_to -- адрес для ответного сообщения (обычно он же является и адресом отправителя). При отсылке MBAPI-сообщения нужно указать оба адреса.
Отсылка MBAPI-сообщения называется процедурой маршрутизации (routing). Для отсылки сообщения необходимо создать экземпляр сообщения и вызывать одну из функций mbapi_3::router::route:
demo_msg_t demo( ... ); mbapi_3::router::route( mbapi_3::mbox_dest_t( "receiver" ), demo, mbapi_3::mbox_dest_t( "sender" ) );
После обращения к route в дело вступает часть MBAPI, ответственная за маршрутизацию сообщения. Эта часть MBAPI контролирует состояние коммуникационных каналов и владеет картой, в которой сохранены имена известных mbox-ов и идентификаторы каналов, через которые эти mbox-ы доступны.
При обнаружении нового транспортного канала MBAPI связывается с удаленной стороной и производит процедуру handshake, в процессе которой стороны обмениваются информацией о доступных на каждой стороне mbox-ах.
При появлении очередного исходящего MBAPI-сообщения MBAPI определяет транспортный канал, в которое сообщение должно быть направлено. После чего сериализует всю связанную с сообщением информацию в экземпляр сообщения специального глобального агента и отсылает это транспортное сообщение в нужный канал. Если же mbox не доступен для приложения, то сообщение теряется.
Особенностью маршрутизации MBAPI сообщений является то, что в отличии от SObjectizer-сообщений MBAPI-сообщения клонируются. Т.е. маршрутизируются не исходный C++ объект (как в случае с SObjectizer-сообщениями), а его копия. Это, с одной стороны, ведет к некоторым издержкам. Но с другой стороны избавляет от ряда проблем, присущих SObjectizer-сообщениям.
Отсылка MBAPI-сообщений выполняется с помощью функций route. А вот получение MBAPI-сообщений выполняется посредством двух дополнительных сущностей: анализаторов и почтальонов.
Анализатор -- это объект производного от mbapi_3::router::analyzer_t класса. Задача анализатора в том, чтобы получить объект MBAPI-сообщения и сказать MBAPI: заинтересован ли получатель в этом экземпляре или нет. А получатель может быть заинтересован в разных сообщениях. Например:
Для построения анализаторов наиболее распространенных типов MBAPI предоставляет набор готовых "кирпичиков". Например, вот так можно создать анализатора сообщений, которые идут на конкретный адрес с конкретного адреса:
mbapi_3::router::and_analyzer( mbapi_3::router::dest_equality_analyzer( to_address ), mbapi_3::router::reply_to_equality_analyzer( from_address ) )
После того, как анализатор укажет MBAPI, что в MBAPI-сообщении кто-то заинтересован, в дело вступает почтальон. Задача почтальона заключается в доставке MBAPI-сообщения получателю.
Понятие почтальона (определяемого интерфейсом mbapi_3::router::postman_t) появилось еще во времена, когда MBAPI использовался в составе не-SObjectizer приложений. Почтальон скрывал от MBAPI детали способа доставки MBAPI-сообщений получателю. В случае SObjectizer-приложений доставка выполняется с помощью отсылки специальных SObjectizer-сообщений.
Анализатор и почтальон образуют пару. В MBAPI эти пары регистрируются специальным образом. Каждая пара имеет приоритет. При получении очередного входящего сообщения MBAPI просматривает список зарегистрированных пар анализатор/почтальон в порядке убывания приоритета. Сообщение доставляется всем почтальонам, для которых анализаторы подтвердили заинтересованность в сообщении.
При регистрации анализатора/почтальона в MBAPI можно указать, что сообщение перехватывается ими. В этом случае, если анализатор подтверждает заинтересованность в сообщении, MBAPI прекращает дальнейший просмотр списка. Т.е. менее приоритетные анализаторы/почтальоны это сообщение уже не получат.
С входящим MBAPI-сообщением связано три сущности: адрес получателя (to), экземпляр сообщения (msg) и адрес отправителя (reply_to). Поскольку сейчас получателями MBAPI-сообщений являются SObjectizer-агенты, то нужен способ по преобразованию MBAPI-сообщений и адресов в SObjetizer-сообщения.
В принципе, разработчик может сам определить свой способ такого преобразования. Для этого нужно разработать собственный тип SObjectizer-сообщения и собственный тип почтальона. Но, поскольку в подавляющем большинстве случаев структура таких сообщений и почтальонов будет одинаковой, в MBAPI уже есть необходимые средства: шаблонные типы mbapi_3::router::so_msg_templ_t и mbapi_3::router::so_msg_templ_postman_t.
При их использовании подписка SObjectizer-агента на MBAPI-сообщения состоит из следующих шагов:
Определение с помощью typedef типа SObjectizer-сообщения в соответствующем агенте:
class a_demo_msg_processor_t : public so_4::rt::agent_t { public : typedef mbapi_3::router::so_msg_templ_t< demo_msg_t, mbox_dest_t, mbox_dest_t > msg_demo; ... };
Описание этого SObjectizer-сообщения для SObjectizer:
SOL4_CLASS_START( a_demo_msg_processor_t ) MBAPI3_ROUTER_SOL4_MSG( msg_demo, a_demo_msg_processor_t::msg_demo ) ... SOL4_CLASS_FINISH()
Описание события для обработки этого сообщения:
class a_demo_msg_processor_t : public so_4::rt::agent_t { public : typedef mbapi_3::router::so_msg_templ_t< demo_msg_t, mbox_dest_t, mbox_dest_t > msg_demo; ... void evt_demo( const msg_demo & cmd ); }; SOL4_CLASS_START( a_demo_msg_processor_t ) MBAPI3_ROUTER_SOL4_MSG( msg_demo, a_demo_msg_processor_t::msg_demo ) ... SOL4_EVENT_STC( evt_demo, a_demo_msg_processor_t::msg_demo ) ... SOL4_CLASS_FINISH()
Подписка SObjectizer-события на SObjectizer-сообщение:
void a_demo_msg_processor_t::so_on_subscription() { so_subscribe( "evt_demo", "msg_demo" ); ... }
Самое главное: создание анализатора/почтальона для MBAPI-сообщения (обычно это делается в конструкторе агента):
a_demo_msg_processor_t::a_demo_msg_processor_t( ... ) { so_add_destroyable_traits( new mbapi_3::router::so_msg_templ_postman_t< msg_demo >( // Имя агента, которому нужно доставлять сообщение. so_query_name(), // Имя SObjectizer сообщения. "msg_demo", // Анализатор для сообщения. mbapi_3::router::and_analyzer( mbapi_3::router::dest_equality_analyzer( to_address ), mbapi_3::router::reply_to_equality_analyzer( from_address ) ) ) ); }
Поскольку so_msg_templ_postman_t производен от so_4::rt::agent_traits_t, то его можно сделать свойством агента. А это позволяет почтальону зарегистрироваться в MBAPI в момент регистрации агента в SObjectizer и дерегистироваться в MBAPI в момент дерегистрации агента. Т.е. so_msg_templ_postman_t будет работать только пока соответствующий агент зарегистрирован в SObjectizer-е.
Для того, чтобы перехватить MBAPI-сообщение необходимо добавить два дополнительных аргумента при создании почтальона:
a_demo_msg_processor_t::a_demo_msg_processor_t( ... ) { so_add_destroyable_traits( new mbapi_3::router::so_msg_templ_postman_t< msg_demo >( // Имя агента, которому нужно доставлять сообщение. so_query_name(), // Имя SObjectizer сообщения. "msg_demo", // Анализатор для сообщения. mbapi_3::router::and_analyzer( mbapi_3::router::dest_equality_analyzer( to_address ), mbapi_3::router::reply_to_equality_analyzer( from_address ) ), // Приоритет анализатора. 1, // Перехват сообщения. mbapi_3::router::interception_enabled ) ); }
В некоторых случаях перехват выполняется для модификации сообщения и последующей перемаршутизации. Но в таком случае может произойти зацикливание: перехватчик будет постоянно захватывать сообщение, преобразовывать его, заново отсылать и вновь захватывать.
Для того, чтобы разорвать этот круг, можно воспользоваться т.н. информацией о перемаршрутизации (rerouting history). Для этого, во-первых, необходимо специальным образом описать тип SObjectizer-сообщения:
typedef mbapi_3::router::so_msg_templ_t< demo_msg_t, mbox_dest_t, mbox_dest_t, // Признак того, что с сообщением связывается // дополнительная информация. true > msg_demo;
В этом случае в типе msg_demo будет находится дополнительное поле с расширенной информацией и метод extended_info(), который позволяет эту информацию получить.
В расширенной информации содержится rerouting history: список имен агентов, которые перехватывали и перемершрутизировали сообщение. Чтобы добавить имя своего агента в этот список, нужно использовать специальный вариант функции route:
void a_demo_msg_processor_t::evt_demo( const msg_demo & cmd ) { ... // Какая-то модификация сообщения... // Перемаршрутизация. mbapi_3::router::route( // Предыдущая история маршрутизации. cmd.extended_info().rerouting_history(), // Наше имя. so_query_name(), // Получатель. destination, // Модифицированное сообщение. updated_msg, // Отправитель. self_mbox ); }
А для того, чтобы анализатор мог определить, что агент уже выполнял перемаршрутизацию сообщения, нужно задать дополнительное условие в почтальоне:
so_add_destroyable_traits( new mbapi_3::router::so_msg_templ_postman_t< msg_demo >( // Имя агента, которому нужно доставлять сообщение. so_query_name(), // Имя SObjectizer сообщения. "msg_demo", // Анализатор для сообщения. mbapi_3::router::and_analyzer( mbapi_3::router::and_analyzer( mbapi_3::router::dest_equality_analyzer( to_address ), mbapi_3::router::reply_to_equality_analyzer( from_address ) ), // Вот этот предикат будет определять, что агент еще не // выполнял перемаршрутизацию сообщения. mbapi_3::router::not_rerouted_by_analyzer( so_query_name() ) ), // Приоритет анализатора. 1, // Перехват сообщения. mbapi_3::router::interception_enabled ) );
Достоинство модели MBAPI-взаимодействия в том, что приложение строится на базе отсылки сообщений в почтовые ящики (mbox-ы). Имена почтовых ящиков либо жестко фиксируются на этапе разработки или же конфигурируются на этапе развертывания приложения. Важно то, что MBAPI-приложение не зависит от физического расположения конкретного mbox-а.
Можно разбить приложение на несколько процессов так, что в каждом процессе будет собственный mbox. А можно разместить все mbox-ы в рамках одного процесса. Для MBAPI-компонентов ничего не изменится -- отсылка MBAPI-сообщений все так же будет выполняться через mbapi_3::router::route, а получение через почтальона. Все различия в расположении mbox-компонентов будут обработаны на уровне MBAPI.