| 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.