Каналы и Фильтры

Сообщение (Message)

Каналы могут буть представлены в виде трубы соединяющей два приложения. Данные помещаются в один коней канала и появляются на другом конце. Однако данные не являются чем-то безструктурным. На практике данные это как правило логически связанные элементы информации, как например, записи, объекты, строки БД и т.п.

 

В то время как при внутрипроцессном взаимодействии, например между потоками, достаточно передать указатель на область памяти от отдного потока другому, при межпроцессном, и тем более, меж-машинном взаимодействии этого уже недостаточно. Для передачи структуры данных в памяти её нужно преобразовать в вид пригодный для передачи.

 

Процесс преобразования структуры в ОП в вид пригодный для передачи по сети называется маршаллинг (marshalling). Обратный процесс называется демаршалинг (unmarshalling).

 

При обмене данными с помощью сообщений данные упаковываются в сообщение которое затем маршалится и передаётся по сети, демаршалится на принимающем конце и распаковывается.

 

Таким образом, любые данные которые необходимо передать через систему обмена сообщениями должны быть в первую очередь представлены в виде сообщения (упакованы в сообщение).

 

Сообщение состоит из двух основных частей:

  1. Заголовок (header) – информация, используемая системой передачи сообщений, которая описывает передаваемые данные, их отправителя, получателя и т.п.
  2. Тело (body) – передаваемые данные. Как правило тело игнорируется системой передачи сообщений и передаётся просто как непрозрачный набор байт.

 

Концепция сообщения в системах обмена сообщениями похожа на трибуты пакетов и конвертов в сетевых протоколах (например TCP/IP) и постовых службах (например Почта России)

 

С точки зрения системы передачи сообщений – все сообщения одинаковые и описываются лишь своими заголовками. В тоже время для интегрируемых приложений сообщения могут иметь различную семантику. Как например сообщения вызывающие какую-либо команду, просто передающие какой-то документ и т.п.

 

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

Для достижения такой функциональности часто прибегают к инкапсуляции её полностью внутри одного компонента, что очень часто приводит к резкому усложнению компонента, затрудняет (а как правило делает вообще невозможным) его повторное использование, затрудняет отладку и тестирование, а так же снижает возможность параллельной работы нескольких разработчиков.

 

Выходом из описанной ситуации может послужить применение шаблона Цепочка ответственности (Chain of Responsibility), который применительно к системам обмена сообщениями и в соответствующих архитектурах носит названия Каналы и Фильтры (Pipes and Filters). В соответствии с этим шаблоном, функциональность разбивается на отдельные компоненты, которые называются Фильтрами. Фильтры имеют как минимум один входной и один выходной интерфейс (например, метод, который принимает один параметр и возвращает одно значение). Этот интерфейс должен быть одинаковый для всех фильтров, т.е. фильтры являются взаимозаменяемыми по интерфейсу. Фильтры объединяются в цепочки при помощи каналов (Pipe) в нужном порядке. Таким образом, в результате получается необходимая суммарная функциональность чётко декомпозированная по группам в виде отдельных фильтров.

 

Положительными особенностями такого подхода является следующее:

  • Возможность независимой и параллельной разработки отдельных фильтров и каналов.
  • Возможность независимого тестирования фильтров.
  • Возможность использования существующей инфраструктуры каналов.
  • Высокая степень переиспользования отдельных фильтров.
  • Большая гибкость, так как для изменения состава функций может.
  • потребоваться лишь изменения конфигурации цепочек фильтров.
  • Возможность параллельной обработки сообщений.
  • Возможность асинхронной обработки сообщений.

 

Однако такой подход не лишён недостатков. Среди них наиболее значительными являются:

  • Унификация входных и выходных интерфейсов фильтров может приводить к необходимости оперировать абстракциями слишком высокого уровня, что часто затрудняет понимание исходного кода. (Например, метод byte[] decrypt(byte[] cipheredBuffer) говорит гораздо больше о том, что он делает, нежели метод Message process(Message msg)). Выражаясь более простым языком – программировать становится сложнее.
  • Необходимость применения унифицированных механизмов передачи сообщений (Pipes) приводит к частому преобразованию данных из внутреннего представления во внешнее, понятное каналу (там где можно в классическом варианте обойтись передачей указателя на структуру данных в памяти, приходится задействовать механизмы маршалинга и демаршалинга), что при большой длине цепочки и при интенсивной нагрузки может резко отрицательно сказаться на производительности (в первую очередь увеличение длительности прохождения сообщения через всю цепочку каналов и фильтров, а кроме того и вообще снижение производительности, из-за увеличенных накладных расходов).
  • Применения большого количества каналов (Pipes) приводит к дополнительному расходу ресурсов. Так, хоть канал и является довольно-таки легковесным ресурсом, он всё-таки потребляет оперативную память для своих внутренних структур.

 

Самым очевидной реализацией канала (Pipe) является канал передачи сообщений (Message Channel) предоставляемый инфраструктурой обмена сообщениями (Messaging Subsystem). Однако, такой подход, в случае если фильтры находятся в рамках одного адресного пространства (в рамках одного процесса) может быть заменён на более эффективный способ обмена данными. Поэтому хорошее архитектурное решение не должно делать предположение о нижележащей реализации каналов. Это позволит в случае простых однопроцессных конфигураций существенно повысить эффективность использования системных ресурсов, а так же повысить производительность.

 

В самом простом случае фильтр имеет один входной и один выходной интерфейс, однако, фильтры могут иметь так же и несколько входных и несколько выходных интерфейсов (например, в случае с фильтрами-маршрутизаторами сообщений, Message Router).

 

Цепочки фильтров могут быть организованы двумя способами:

  • Последовательно – каждый фильтр имеет один входной и один выходной интерфейс и выход одного фильтра строго подаётся на вход другого фильтра, более того, на вход одного фильтра может поступать только выход одного какого-либо фильтра.

 

 

 

 

  • Параллельно – выход одного фильтра может подаваться на вход сразу нескольких фильтров.

 

Параллельная обработка (если она принципиально возможна) способна сократить время прохождения запроса (или обработки сообщения) по цепочки фильтров, а так же повысить пропускную способность. Более того, в некоторых случаях фильтры могут представлять собой некоторую функциональность, которая может быть выполнена только на определённом компьютере (например, запрос к какому либо централизованному серверу). Соответственно, если удаётся сделать несколько запросов параллельно – то это может благотворно сказаться на общей производительности решения. Параллельность, однако, возможна лишь в том случае если входные параметры фильтров выполняющихся параллельно не зависят от выходных значений друг-друга (в указанно примере входные параметры фильтров B и C завысят от выходных значений фильтра A, но они не зависят друг от друга, поэтому они в принципе могут выполняться параллельно).