Краткий обзор MBAPI

Author: Евгений Охотников
Contact: eao197 at intervale dot ru; eao197 at yahoo dot com
Date: 2007.08.26

Contents

История

Библиотека 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).

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.

Hosted by uCoz