============================== Краткий обзор MBAPI ============================== :Author: Евгений Охотников :Contact: eao197 at intervale dot ru; eao197 at yahoo dot com :Date: 2007.08.26 .. _SObjectizer: http://sobjectizer.sourceforge.net .. contents:: История ======= Библиотека MBAPI (Message Box API) появилась как попытка красиво разрешить одну задачу, где намечалось несколько интересных условий. Во-первых, предполагалась рассылка сообщений клиентам некоей системы. Клиент мог настраивать способ получения сообщения: в виде SMS на телефон, в виде письма на e-mail или какой-нибудь еще. Систему предполагалось делать в виде нескольких модулей. Один из них формировал сообщение и выбирал адрес, а другой занимался доставкой по конкретному виду транспорта в зависимости от типа адреса. Вторая особенность заключалась в том, что генерирующий сообщения модуль должен был быть написан на C++, но без использования SObjectizer. А транспортный модуль должен был быть написан на C++ с использованием SObjectizer. В таких условиях появилась идея создать транспортную библиотеку MBAPI, которая бы использовала средства SObjectizer-а для построения распределенных приложений (т.е. сообщения глобальных агентов), но при этом: - была бы более высокоуровневой. Так, чтобы при использовании MBAPI программисту не нужно было бы отслеживать состояние транспортных каналов и хранить актуальные значения comm_channel для существующих в данный момент транспортных каналов; - поддерживала более сложные структуры данных, чем это доступно с помощью сообщений SObjectizer-агентов. И при этом чтобы существовала возможность сохранения двоичного образа сообщения (в файле или БД) с последующим восстановлением сообщения из сохраненного двоичного образа; - поддерживала перехват и перемаршрутизацию сообщений; - была возможность встраивать MBAPI в не-SObjectizer C++ приложения. Для достижения этих целей и была разработана библиотека 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``). To, reply_to и routing ---------------------- С каждым сообщением в 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-сообщений и почтальоны SObjectizer-сообщений -------------------------------------------------------------- Отсылка MBAPI-сообщений выполняется с помощью функций ``route``. А вот получение MBAPI-сообщений выполняется посредством двух дополнительных сущностей: анализаторов и почтальонов. Анализатор -- это объект производного от ``mbapi_3::router::analyzer_t`` класса. Задача анализатора в том, чтобы получить объект MBAPI-сообщения и сказать MBAPI: заинтересован ли получатель в этом экземпляре или нет. А получатель может быть заинтересован в разных сообщениях. Например: - в конкретном типе сообщений, идущих на конкретный mbox; - в конкретном типе сообщений, идущих с конкретного mbox-а; - в конкретном типе сообщений, идущих на конкретный mbox с конкретного mbox-а; - в любых сообщениях, идущих на конкретный mbox; - в любых сообщениях, идущих с конкретного mbox-а; - и т.д. вплоть до "любых сообщений". Для построения анализаторов наиболее распространенных типов 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- и SObjectizer-сообщения ------------------------------ С входящим 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 ) ); Прозрачность расположения mbox-ов ================================= Достоинство модели MBAPI-взаимодействия в том, что приложение строится на базе отсылки сообщений в почтовые ящики (mbox-ы). Имена почтовых ящиков либо жестко фиксируются на этапе разработки или же конфигурируются на этапе развертывания приложения. Важно то, что MBAPI-приложение не зависит от физического расположения конкретного mbox-а. Можно разбить приложение на несколько процессов так, что в каждом процессе будет собственный mbox. А можно разместить все mbox-ы в рамках одного процесса. Для MBAPI-компонентов ничего не изменится -- отсылка MBAPI-сообщений все так же будет выполняться через ``mbapi_3::router::route``, а получение через почтальона. Все различия в расположении mbox-компонентов будут обработаны на уровне MBAPI. .. vim:ts=2:sw=2:sts=2:expandtab:tw=78:ft=rst: