Личный сайт Алексея Григорьева

Enterprise Integration Patterns

Enterprise Integration Patterns — шаблоны и стратегии интеграции корпоративных приложений с помощью механизмов обмена сообщениями. Данная статья — конспект информации с сайта http://www.eaipatterns.com/ и из одноименной книги.

Список шаблонов

Integration Styles

Messaging Systems

Messaging Channels

Message Construction

Message Routing

Message Transformation

Messaging Endpoints

System Management

Integration Styles

Способы интеграции приложений

File Transfer


Каким образом можно интегрировать несколько приложений так, чтобы они могли работать вместе и обмениваться информацией?


Приложения можно интегрировать, используя файловую систему. Одно из приложений создает файлы, содержащие необходимую информацию, другое — считывает эти файлы.
Формат файлов может быть любым — текст, XML, и т.п. Важно, чтобы формат для всех приложений был согласованный.

Shared Database


Каким образом можно интегрировать несколько приложений так, чтобы они могли работать вместе и обмениваться информацией?


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

Remote Procedure Invocation


Каким образом можно интегрировать несколько приложений так, чтобы они могли работать вместе и обмениваться информацией?


Используйте RPI для обмена информацией с помощью вызова методов другого приложения удаленно.

Messaging


Каким образом можно интегрировать несколько приложений так, чтобы они могли работать вместе и обмениваться информацией?


Используйте механизм сообщений для отправки и получения данных.

Данные в сообщениях можно передавать часто, быстро, надежно и асинхронно. Отправка сообщений не требует чтобы обе системы работали в одно и то же время — механизм отправки и получения позаботится о гарантированной доставке.

Messaging Systems

Системы для рассылки и получения сообщений. Далее для термина «Messaging System» будет использоваться «система рассылки сообщений».

Message Channel


Как одно приложение может обмениваться данными с другим используя механизм сообщений?


Для обмена данными приложения соединяются с помощью MessageChannel — каналов для обмена информацией. Одно приложение пишет в канал, другое приложение считывает из канала.

Канал — это средство для доставки сообщений. Приложение, отправляющее сообщение, не знает, где, в конечном счете, сообщение будет обработано, таким образом, уменьшая связанность между компонентами системы.

Паттерны, связанные с MessageChannel: PointToPointChannel, PublishSubscribeChannel, DatatypeChannel, InvalidMessageChannel, DeadLetterChannel, GuaranteedDelivery, ChannelAdapter, MessagingBridge, MessageBus.

Message

Как два приложения, связанные между собой с помощью MessageChannel, могут обмениваться информацией?

Для этого информация оборачивается в сообщение — Message — единица данных, которая может быть передана через MessageChannel

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

Header — заголовок сообщения, описывает данные, источник, пункт назначения и т.п.
Body — содержит сами данные.

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

Шаблоны, связанные с сообщениями: CommandMessage, DocumentMessage, EventMessage, RequestReply, ReturnAddress, CorrelationIdentifier, MessageSequence, MessageExpiration.

Pipes and Filters


Каким образом организовать сложную обработку сообщений, при придерживаясь слабой связанности между отдельными компонентами обрабатывающей системы?

Используйте подход «PipesAndFilters», чтобы разбить сложную обработку сообщений на последовательность небольших, независимых между собой, обрабатывающих шагов (Filters, фильтров), соединенных между собой с помощью каналов (Pipes).

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

Связанные шаблоны: MessageRouter, ContentFilter, MessageFilter, MessageRouter, Resequencer.

Message Router


Как организовать доставку сообщений тем или иным фильтрам на основе определенного набора условий, при этом сохраняя систему слабо связанной?

Используйте фильтр специального назначения — MessageRouter — который принимает сообщения из одного канала, и в зависимости от соблюдения тех или иных условий, выбирает канал, в которое сообщение будет отправлено.

MessageRouter присоединяется к нескольким выходящим каналам. Он не изменяет содержания сообщений, только выбирает необходимый пункт назначения.

Связанные шаблоны: PipesAndFilters, DynamicRouter, MessageFilter.

Message Translator


Каким образом системы, использующие данные различных форматов, могут обмениваться сообщениями между собой?


Для этого необходимо использовать MessageTranslator — преобразователь сообщения из одного формата данных в другой.

Шаблон

Это эквивалент шаблону проектирования Адаптер [GoF] для систем обмена сообщениями.

Связанные шаблоны: EnvelopeWrapper, ContentEnricher, ContentFilter, ClaimCheck, Normalizer, CanonicalDataModel.

Message Endpoint


Каким образом приложение соединяется с каналом для отправки и получения сообщений?


Для соединения приложения с каналом используется MessageEndpoint — клиент системы отправки сообщений, который может быть использован приложением для передачи данных используя сообщения.

Связанные шаблоны: MessagingGateway, MessagingMapper, TransactionalClient, PollingConsumer, EventDrivenConsumer, CompetingConsumers, MessageDispatcher, SelectiveConsumer, DurableSubscriber, IdempotentReceiver, ServiceActivator.

Messaging Channels

Point-to-Point Channel


Каким образом нужно отправлять сообщения, чтобы быть уверенным, что только один получатель получит и обработает сообщение?


Для гарантированной обработки сообщения только одним получателем отправляйте сообщение в PointToPointChannel.

Однако такой канал может иметь нескольких подписчиков, но только один из них может получить и обработать сообщение.

Publish-Subscribe Channel


Как отправитель может отправить сообщение всем заинтересованным получателям?


Для отправки копии сообщения всем заинтересовавшимся подписчикам, используйте PublishSubscribeChannel.

PublishSubscribeChannel представляет собой расширенную версию шаблона Наблюдатель [GoF] для систем отправки сообщений.

Datatype Channel


Каким образом отправляющее приложение может быть уверено в том, что получатель точно будет знать, как обработать передаваемые данные?


Для каждого типа данных в сообщениях используйте отдельный канал — таким образом, все сообщения на одном и том же канале будут одного и того же типа.

Однако создание слишком большого количества каналов в конечном счете может привести к слишком большому потреблению памяти, ухудшению качества кода программы и т.п. Для очень большого количества данных следует использовать SelectiveConsumer или ContentBasedRouter.

Invalid Message Channel


Каким образом получатель может обрабатывать бесполезные сообщения или сообщения с ошибками?


Получатель должен перенаправить бесполезное сообщение в специальный канал InvalidMessageChannel — канал для сообщений, которые не могут быть обработаны их получателями.

Для отладочных целей в системе почти всегда нужен хотя бы один InvalidMessageChannel — он может служить системой логирования ошибок в системе.

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

Похожая концепция лежит в основе в DeadLetterChannel. Отличие состоит в том, что InvalidMessageChannel используется для сообщений, которые не могут быть обработаны получателем, а DeadLetterChannel — для сообщений, которые не могут быть доставлены.

Dead Letter Channel


Что система отправки сообщений должна сделать с сообщениями, которые не могут быть доставлены?


Как только система определяет, что сообщение не может быть доставлено, она может переместить его в DeadLetterChannel.

Guaranteed Delivery


Каким образом отправитель сообщения может быть уверенным, что сообщение будет доставлено в любом случае, даже если система отправки сообщений на время выйдет из строя?

Преимущество систем отправки сообщений в том, что получатель не должен быть все время доступным. Так же, как и сетевое подключение машины, на которой находится отправитель. Если сеть не работает, то сообщение находится в памяти до тех пор, пока связь не появится. Если получатель недоступен, система будет пытаться отправить сообщение снова и снова, пока отправитель не появится в сети. Однако подобные сообщения сохраняются в оперативной памяти, а это значит, что если система выйдет из строя (из-за перебоя энергии, выхода из строя процесса и т.п.), то все данные будут утеряны. Для предотвращения этого, сообщения могут быть сохранены в постоянное хранилище — например, базу данных.


Используйте GuaranteedDelivery для сохранения копий сообщений в постоянное хранилище — таким образом, предотвращая утёрю сообщения даже в случае, если в системе произойдет сбой.

Однако, если данные в сообщении не являются критичными, т.е. потеря сообщения допустима, то следует избегать использования GuaranteedDelivery, так как подобное решение может замедлять работу компонента. Так же может быть полезно иметь возможность включать и выключать GuaranteedDelivery на время тестирования и отладки.

Channel Adapter


Каким образом можно присоединить приложение к системе отправки сообщений так, чтобы оно могло участвовать в обмене данными?


При добавлении приложения в систему, используйте ChannelAdapter — класс, который может взаимодействовать с API приложения или с его данными (например, через базу данных) и отправлять сообщения на основе этих данных. И наоборот, получать сообщения и вызывать необходимые функции приложения.

В данном случае ChannelAdapter выступает в роле клиента системы отправки сообщений и вызывает функции самого приложения. Аналогично, адаптер слушает внутренние события приложения и посылает сообщения в ответ на эти события.

ChannelAdapter может быть присоединен к различным слоям архитектуры приложения: к UI, слою бизнес-логики или к база данных.

Messaging Bridge


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


Используйте MessagingBridge — соединение между двумя различными системами, которое дублирует сообщения из одной системы в другую.

Практически, обычно нет необходимости в соединении двух систем целиком, поэтому часто соединяются только некоторые каналы. Таким образом, MessagingBridge обычно представляет собой ChannelAdapter, в данном случае являющийся посредником не между API приложения и системой сообщений, а между двумя системами отправки сообщений.

MessagingBridge так же выступает в роли MessageTranslator, когда передаваемые сообщения разных форматов.

Message Bus


Каким образом можно организовать взаимодействие нескольких приложений так, чтобы они могли работать вместе, но оставались слабосвязанными — так, чтобы одно из

Каким образом можно организовать взаимодействие нескольких приложений так, чтобы они могли работать вместе и обмениваться информацией, но были независимыми друг от друга — так, чтобы любое приложение можно было легко добавить или удалить без последствий для других приложений?


Используйте MessageBus для организации взаимодействия приложений между собой с помощью обмена сообщениями.

Для MessageBus характерно:

- Общая коммуникационная инфраструктура (MessageRouter или PublishSubscribeChannel)
- Использование адаптеров — интерфейсов для взаимодействия с MessageBus
- CanonicalDataModel, общая для всех приложений структура команд — все участники системы должны понимать команды и реагировать на них

MessageBus похож на шину, используемую в компьютерах — средство обмена данными между CPU, памятью и периферией.

Message Construction

Command Message


Каким образом можно использовать сообщения не для передачи данных, а для вызова функций других приложений?

Для ООП программ существует шаблон Команда [GoF], инкапсулирующий команду в объект. В сообщениях можно использовать точно такой же подход.


Используйте CommandMessage для вызова процедур других приложений.

Для CommandMessage не используется специальный тип данных, это простое сообщение, которое содержит инструкцию для получателя. Это может быть текст, xml или сериализованый объект. Обычно CommandMessage оправляется на PointToPointChannel — для того, чтобы гарантировать выполнение команды только одним получателем и только один раз.

Document Message


Каким образом можно использовать сообщения для передачи данных?


Используйте DocumentMessage.

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

Обычно отправляется, используя PointToPointChannel для предотвращения создания нескольких экземпляров одного и того же документа.

В шаблоне RequestReply ответ обычно представляет собой DocumentMessage.

Event Message


Каким образом можно использовать сообщения для передачи информации о наступлении тех или иных событий?

Иногда внутри объекта происходит какое-либо событие, о котором другие объекты должны быть уведомлены. Например. в MVC, Model, при изменении, оповещает View.


Используйте EventMessage для асинхронного уведомления о событии всех заинтересованных приложений.

Если внутри приложения возникает событие, о котором оно должно сообщить другим приложениям, оно создает EventMessage и отправляет всем заинтересовавшимся подписчикам.

Разница между EventMessage и DocumentMessage — во времени и в содержании. Содержание сообщения с событием обычно не имеет значения, иногда события содержат только пустое тело. Они просто информируют получателя о наступлении события. Так же DocumentMessage обычно обязателен к обработке, тогда как EventMessage можно свободно проигнорировать в том случае, если получатель не заинтересован в событии.

Существует две модели уведомления приложений о событиях:
- Push Model отправляет всю информацию о событии в сообщении. Более эффективно, если все получатели будут использовать всю полученную информацию.
- Pull Model отправляет минимальное количество информации, и если получателю нужно, он использует ее для получения остальной части

Обмен сообщениями в Pull-модели происходит в три этапа:
1. Update — приложение посылает подписчикам EventMessage с уведомлением.
2. State Request — заинтересовавшиеся подписчики отправляют CommandMessage — запрос на получение дальнейших деталей.
3. State Reply — подписчики получают DocumentMessage — ответ на запрос с деталями о событии.

EventMessage обычно отправляется на PublishSubscribeChannel — таким образом, на событие могут подписаться несколько приложений.

Request-Reply


Каким образом приложение может получать ответ на запрос?

Обмен сообщениями представляет собой одностороннюю коммуникацию — связь производится только в одном направлении, от отправителя к получателю. Но иногда нужна двухсторонняя коммуникация — в случаях, когда получатель должен сообщить результат обработки запроса, отправить уведомление о получении сообщения и т.п.


Используйте RequestReply — два сообщения, образующие пару запрос-ответ, каждое из которых отправляется по собственному каналу связи.

RequestReply состоит из двух участников

- Requester — отправляет сообщение и ожидает ответ.
- Replier — получает сообщения, обрабатывает его и посылает результат в ответ.

Канал, по которому отправляется запрос (Request Channel) может быть как PointToPointChannel, так и PublishSubscribeChannel.

Существуют два способа организовать ожидание ответа отправителем

1. Synchronous Block — приложение останавливается и ждет, пока не будет доставлен ответ на запрос.
2. Asynchronous Callback — приложение не останавливается, а во время доставки ответа срабатывает вызов соответствующей процедуры (callback).

Шаблон RequestReply может представлять из себя:

1. Организацию RPC (Remote procedure call) — реализация RemoteProcedureInvocation с помощью сообщений (синхронное).
2. Messaging Query — запрос содержит CommandMessage, ответ — MessageSequence (асинхронное).
3. Notify/Acknowledge — запрос содержит EventMessage, ответ — DocumentMessage уведомление о получении информации о событии.

Ответ может представлять из себя:

1. Void — простое уведомление о том, что задача выполнена.
2. Result Value — результат вычислений.
3. Exception — информация об исключительной ситуации, возникшей во время выполнения команды.

Запрос обязательно должен содержать ReturnAddress. Ответ должен содержать CorrelationIdentifier для определения, на какого запроса вычислен этот ответ.

Return Address


Каким образом получатель (Replier) в шаблоне RequestReply может узнать, куда нужно отправлять ответ?

В шаблоне RequestReply, запрос имеет отношение один-к-одному с ответом, т.е. должна быть возможность отправить ответ именно на тот канал, на котором ожидает ответа приложение, отправившее запрос.


Сообщение с запросом должно содержать ReturnAddress — обратный адрес, на который будет отправлен ответ.

Таким образом, получатель (Replier) не нужно беспокоиться об адресе получателя, он может его извлечь из сообщения с запросом. ReturnAddress часть заголовка (Header) сообщения.

Correlation Identifier


Каким образом отправитель (Requester), получивший ответ на запрос, может узнать, для какого именно запроса был прислан ответ?


Каждый ответ должен содержать CorrelationIdentifier — уникальный идентификатор, который однозначно определяет, для какого именно запроса пришел ответ.

Шаблон CorrelationIdentifier состоит из шести участников:

1. Requestor — отравитель; приложение, отправляющее запрос и ожидающее ответ.
2. Replier — получатель; приложение, получающее запрос, выполняющее его и отправляющее ответ.
3. Request — сообщение, отправленное от отправителя к получателю, содержащее CorrelationIdentifier.
4. Reply — сообщение с ответом, содержащее CorrelationIdentifier.
5. RequestId — токен, уникально идентифицирующий запрос.
6. CorrelationIdentifier — то же самое значение, что и RequestId.

CorrelationIdentifier обычно содержится в заголовке (Header) сообщения.

Message Sequence


Каким образом можно использовать сообщения для отправки большого количества данных?

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


Если необходимо передать большой объём данных, используя сообщения,
Для отправки большого количества данных, используя сообщения, их нужно разбить на кусочки небольшой длины и отправлять как MessageSequence — последовательность из сообщений.

Каждое сообщение в последовательности имеет три идентифицирующих поля

1. SequenceId — определяет эту последовательность данных от другой
2. PositionId — определяет порядковый номер сообщения в последовательности
3. Size — общее количество сообщений в последовательности
или
3. End Mark — пометка, определяющая последнее сообщение в последовательности

Если отправитель изначально знает общее количество сообщений, он использует поле с размером, иначе — EndMark.

Если последовательность — ответ в шаблоне RequestReply, то SequenceId обычно точно такой же, как и CorrelationIdentifier.

Message Expiration


Каким образом отправитель может обозначить срок действия сообщения — срок, по истечению которого, сообщение считается неактуальным, и не может быть обработано?

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


Установите MessageExpiration на определенный промежуток времени, по истечению которого сообщение считается неактуальным.

Как только заданный промежуток времени проходит, и сообщение так и не было доставлено, у него «истекает срок годности». Большинство систем отправляют такие сообщения в DeadLetterChannel, некоторые же просто игнорируют такое сообщение.

Свойства, которые могут быть заданы с сообщениях со сроком годности:
- MessageExpiration — временной промежуток. Может быть абсолютным (когда истекает) или относительным (через сколько).
- Дата и время отправки сообщения.

Если в системе обнаруживается сообщение с истёкшим сроком, оно отправляется в DeadLetterChannel. Если же получатель получает такое сообщение, он должен направить его в InvalidMessageChannel.

Format Indicator

Каким образом формат данных в сообщениях может быть спроектирован с учётом вероятных изменений в будущем?

Разрабатываемый формат данных должен включать FormatIndicator — информацию о том, какой тип данных передается и какой формат данных используется.

FormatIndicator позволяет отправителю сообщить формат отправляемых данных.

Способы реализовать FormatIndicator:

1. VersionNumber — номер, строка или другой идентификатор, уникально определяющий формат данных. Каждый из участников обмена сообщениями должен знать, как можно получить доступ к описанию формата и как эти данные интерпретировать.
2. ForeignKey — имя файла, url, или другой идентификатор, ссылающийся на описание формата документа.
3. FormatDocument — формат документа включается в само сообщение. Клиенту не нужно каждый раз запрашивать формат, но это увеличивает передаваемый объём информации.

Message Routing

Шаблоны, относящиеся к маршрутизации сообщений. Шаблоны этой группы можно разделить на три подгруппы:

1. SimpleRouters — простые вариации шаблона MessageRouter
2. ComposedRounters — шаблоны, включающие в себя элементы нескольких более мелких шаблонов
3. Architectural — архитектурные шаблоны

Content-Based Router


Что делать, если реализация одной функции разбросана по разным частям системы?


Используйте ContentBasedRouter для направления сообщения определённому получателю на основе содержания сообщения.

ContentBasedRouter изучает содержание сообщение, и, на основе этого, перенаправляет сообщение на необходимый канал.

Message Filter


Каким образом компонент может избежать получения ненужных для него сообщений?


Используйте MessageFilter для того, чтобы перенаправлять получателю только те сообщения, в которых он заинтересован, на основе заданных критериев.

MessageFilter — это MessageRouter с единственным выходным каналом. Если контент подходит под критерий отбора, MessageFilter отправляет сообщение далее.

Разница между MessageFilter и SelectiveConsumer в том, что SelectiveConsumer сам решает, какие сообщения он может обработать, а какие нет, MessageFilter же фильтрует сообщения так, что получателю нет необходимости об этом заботиться.

Dynamic Router


Каким образом можно избежать зависимости маршрутизатора от всех возможных пунктов назначения?

Если список всех возможных получателей часто меняется, то поддержка MessageRouter легко может стать обузой.


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

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

DynamicRouter может сохранять все правила в специальном хранилище. Если в компоненте произойдет сбой, то он может восстановить конфигурацию из базы. Или, при перезагрузке, он может опросить все компоненты вновь.

Так как все получатель независимы друг от друга, DynamicRouter может столкнуться с необходимостью разрешать конфликты между правилами получателей.
Способы решения могут включать в себя:

1. Игнорирования правил, которые конфликтуют с уже существующими.
2. Во время обработки выбирать первого получателя, подходящего под правила, и не отправлять остальным.
3. Отправлять сообщение всем получателям, чьи правила подходят. Однако это правило превращает DynamicRouter в RecipientList.

Recipient List


Каким образом перенаправлять сообщения к списку получателей, задаваемому динамически?


Используйте RecipientList для отправления сообщения списку получателей на основе задаваемых критериев.

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

Splitter


Каким образом можно обработать отдельные элементы сложного сообщения?


Используйте Splitter для разбиения сложного составного сообщения на последовательность отдельных сообщений.

Хоть Splitter и разбивает сообщения, но обычно сохраняет какую-то информацию, общую для всех. (Например, номер заказа — для каждого из элементов заказа.)

Aggregator


Каким образом можно соединить последовательность связанных между собой сообщений в одно?


Используйте Aggregator — фильтр с состоянием, собирающий составное сложное сообщение из последовательности отдельных, но связанных сообщений.

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

При проектировании нужно определить следующие свойства для Aggregator:

- Correlation — какие сообщение связаны с какими? (связь можно установить с помощью CorrelationIdentifier)
- Completeness condition — состояние, при котором Aggregator готов опубликовать результат
- Aggregation Algorithm — алгоритм, используемый для комбинирования нескольких сообщений в одно

Для игнорирования сообщений, задержавшихся на слишком долгое время, можно использовать MessageExpiration.

Стратегии агрегирования
1. Ждать, пока пока все части сообщения получены (обычно с заданным таймаутом, по истечению которого возбуждается исключительная ситуация)
2. TimeOut — ожидание сообщения до тех пор, пока не пройдет определенное время. Можно выбрасывать исключение, если никаких сообщений из последовательности за это время не получено
3. First Best — отправлять только самое первое из прибывших сообщений — в тех случаях, когда время реакции важно
4. Timeout and Override — ждать истечения какого-либо срока или определенного количества сообщений
5. External Events — получать сообщения до тех пор, пока не получено сообщение со специальным событием (EventMessage)

Resequencer


Каким образом можно упорядочить поток взаимосвязанных, но приходящих беспорядочно сообщений?


Используйте Resequencer для упорядочивания сообщений и отправки их в выходной канал в специально заданном порядке.

Resequencer может сохранять сообщения до тех пор, пока не получена полная последовательность, а затем публикует все сообщения в правильном порядке. Каждое из сообщений должно иметь SequenceId (MessageSequence).

Composed Message Processor


Каким образом можно обрабатывать составное сообщение, если каждый из элементов сообщения может требовать определенную, отличную от других, процедуру обработки?

Например, заказ состоит из позиций. И каждая позиция требует проверки наличия на складе, причем каждый элемент на определённом складе.


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

Этот шаблон демонстрирует, как несколько более мелких шаблонов могут быть использованы для создания больших шаблонов, причем последние могут быть использованы, как простой фильтр в архитектуре PipesAndFilters.

Scatter-Gather

Как можно организовать отправку сообщения нескольким получателям, при этом каждый из получателей должен отправить ответ?

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


Используйте ScatterGather, который рассылает сообщение нескольким получателям и затем собирает все ответы в одно составное сообщение.

ScatterGather отправляет сообщение-запрос нескольким получателям, затем использует Aggregator для комбинирования всех ответов в один

Routing Slip


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

Требования:
- Эффективный поток сообщений — сообщения должны проходить только через требуемые компоненты и избегать ненужных
- Эффективное использование ресурсов — не должно использоваться большое количество каналов, роутеров и т.п.
- Гибкость — маршрут отдельного сообщения можно легко изменить
- Легко поддерживаемый — при добавлении нового типа сообщения изменения вносятся только в одном месте


Используйте RoutingSlip — присоединяйте его к каждому сообщению, тем самым определяя, в какой последовательности и какими компонентами сообщение будет обработано. К каждому компоненту присоедините MessageRouter — для чтения RoutingSlip и перенаправления сообщения к следующему компоненту в списке.

Так же добавляется специальный компонент, перед обработкой вычисляющий список требуемых шагов для каждого сообщения, а затем присоединяющий этот список, как RoutingSlip. На каждом шаге обработки текущий компонент смотрит в список и передает сообщение следующему компоненту.

Process Manager


Каким образом организовать маршрутизацию сообщения через несколько этапов обработки, когда этапы неизвестны во время проектирования и могут быть непоследовательными?

При использовании RoutingSlip, логика маршрутизации разбросана вокруг большого количества компонентов — что может привести к сложностям в поддержке.


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

Иными словами, все сообщения после обработки очередным компонентов отравляются в ProcessManager, а там определяется следующий компонент. Таким образом, все сообщения всегда проходят через центральный «хаб». Однако в этом есть недостаток — ProcessManager может стать узким местом в производительности системы.

Message Broker


Каким образом можно отделить (decouple) пункт назначения сообщения от отправителя, при этом поддерживая централизованный контроль над потоком сообщений?

Если соединять каждый компонент с каждым для организации обмена сообщениями, система может превратиться в «интеграционные спагетти».


Используйте центральный компонент MessageBroker, который может получать сообщение от нескольких источников, определять правильный пункт назначения и перенаправлять сообщение в нужный канал связи.

Недостаток у MessageBroker точно такой же, как и у ProcessManager — он может стать узким местом в производительности системы. Однако, MessageBroker не имеет состояния, поэтому есть возможность запускать несколько экземпляров и использовать PointToPointChannel для отправления им сообщений (только один MessageBroker получит сообщение).

Message Transformation

Интегрируемые системы редко имеют соглашения о формате данных, и для преобразования данных из одного формата в другой используются трансляторы.

Envelope Wrapper


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

Например, внешняя система требует, чтобы сообщения содержали логин и пароль пользователя — для того, чтобы избежать неавторизованного доступа к данным, а так же, чтобы сообщения были зашифрованы. И «сырое» сообщение существующей системы нужно перевести в этот формат.


Используйте EnvelopeWrapper для оборачивания данных приложения в «конверт», согласующийся с требованиями системы. После обработки вытащите сообщение из «конверта».

Шаги:
1. Источник публикует сообщение в «сыром» формате — формате, не соответствующим требованиям инфраструктуры.
2. С помощью EnvelopeWrapper сообщение преобразуется в формат, соответствующий требованиям (добавляется необходимый заголовок, шифрование и т.п.).
3. Сообщение обрабатывается инфраструктурой.
4. Сообщение извлекается из конверта — обратно преобразуется в первоначальный формат.
5. Получатель получает сообщение.

Content Enricher


Каким образом приложение может обмениваться сообщениями с другим приложением, если у источника для этого нет всех обязательных данных?


Для дополнения сообщения недостающей информации используйте ContentEnricher. ContentEnricher опрашивает внешние источники и добавляет недостающую информацию в сообщение.

Content Filter


Каким образом упростить обработку большого сообщения в тех случаях, когда вы заинтересованы только в некоторой его части?


Используйте ContentFilter для удаления ненужных данных из сообщения.

ContentFilter может не только удалить ненужные данные, но и упростить структуру («выровнять» дерево и т.п.). Так же может быть использован для удаления конфиденциальной информации из сообщения или для уменьшения траффика.

Claim Check


Каким образом уменьшить объем данных, передаваемых в сообщении, без полного отказа от информации, ненужной на некоторых этапах?

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


Сохраняйте ненужные на промежуточных этапах данные в специальном хранилище и создавайте ClaimCheck для возможности запросить данные обратно.

Шаги

1. Большое сообщение с большим количеством временно ненужных данных прибывает.
2. Генерируется уникальный ключ, который будет использоваться в качестве ClaimCheck.
3. Данные извлекаются из сообщения и сохраняются в хранилище.
4. Сообщение обрабатывается.
5. Компоненты, которые нуждаются в сохраненных данных, запрашивает их, используя ClaimCheck.

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

Normalizer


Каким образом обрабатывать семантически эквивалентные сообщения, приходящие в разных форматах?

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


Используйте Normalizer для перенаправления сообщения в соответствующий преобразователь — таким образом, все сообщения в результате будут иметь один формат.

Normalizer использует по одному MessageTranslator для каждого из типов данных и перенаправляет входящие сообщение к соответствующему MessageTranslator через MessageRouter.

Canonical Data Model

Каким способом можно минимизировать зависимости в случае, если интегрируемые приложения используют сообщения разных форматов?

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


Используйте независимую ни от каких приложений CanonicalDataModel. Обязуйте все приложения следовать этой модели — принимать и отправлять сообщения в одном и том же, общем для всех, формате.

Каким образом приложение может следовать общему формату данных? Существует три варианта:

1. Изменить внутренний формат данных приложения.
2. Реализовать MessagingMapper.
3. Использовать внешний MessageTranslator для преобразования из формата приложения в CanonicalDataModel.

Messaging Endpoints

Messaging Gateway


Каким образом можно скрыть взаимодействие с системой отправки сообщений от остального приложения?
Как инкапсулировать (скрыть) доступ к mess.system от остального приложения?


Используйте MessagingGateway, подсистему, скрывающую вызовы методов системы отправки сообщений и предоставляющую доступ к методам доменной модели приложения.

MessagingGateway инкапсулирует код взаимодействия с сообщениями (отправка, получение и т.п) и отделяет его от всего остального приложения. MessagingGateway является реализацией шаблона Gateway pattern [EAA] для систем сообщений.

Messaging Mapper

Каким образом можно осуществлять взаимодействие между объектами доменной модели и системой сообщений, сохраняя при этом независимость этих двух частей приложений друг от друга?


Используйте MessagingMapper, который содержит логику отображения (mapping logic) между системой сообщений и доменной моделью. Ни доменная модель, ни средства отправки сообщений не должны знать о присутствии MessagingMapper.

MessagingMapper — специализация паттерна Mapper [EAA]

Transactional Client


Каким образом приложения могут контролировать транзакции при взаимодействии с помощью сообщений?


Используйте TransactionalClient — сделайте сессию клиента со всей системой транзакционной таким образом, чтобы клиент сам мог определять границы транзакции.

И отправитель, и получатель могут быть транзакционными. Если транзакционный отправитель — сообщение не добавляется в канал «по-настоящему» до тех пор, пока отправитель не подтвердит (commit) транзакцию. Если транзакционным является получатель, то сообщение не убирается с канала «по-настоящему» до тех пор, пока не получатель сделает подтверждение транзакции.

Polling Consumer


Каким образом приложение может получать и обрабатывать сообщение только тогда, когда оно готово к этому?

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


Приложение должно использовать PollingConsumer — и запрашивать сообщение только тогда, когда оно готово получить и обработать его.

Этот шаблон так же известен, как синхронный получатель (Synchronous Receiver), так как поток (thread) получателя блокируется до тех пор, пока сообщение не получено.

Event-Driven Consumer


Каким образом приложение может получать и обрабатывать сообщение сразу, как только они появляются на канале?

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


Приложение должно использовать EventDrivenConsumer — потребитель, автоматически получающий сообщения сразу, как только они появляются на канале.

Этот шаблон так же известен, как асинхронный получатель (Asynchronous Receiver) (в противоположность PollingConsumer), поток (thread) находится в режиме ожидания до тех пор, пока его не выведет из этого состояния сообщение, только что пришедшее на канал.

Competing Consumers


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

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


Создайте несколько CompetingConsumers на одном и том же канале так, чтобы они могли обрабатывать сообщения в несколько потоков.

CompetingConsumers — это несколько приложений, получающие сообщения с одного и того же PointToPointChannel канала.

Message Dispatcher


Каким образом можно координировать действия нескольких приложений в случае, когда они получают сообщения из одного и того же канала?

Несколько приложений на PointToPointChannel ведут себя как CompetingConsumers, что недопустимо в случаях, если эти приложения не взаимозаменяемы (т.е. ведут себя по-разному).

Шаблон Посредник [GoF] решает эту проблему. Посредник координирует взаимодействие между объектами так, что у них нет необходимости знать друг о друге.


Создайте MessageDispatcher на канале, который будет считывать сообщения из канала и распределять их между получателями.

MessageDispatcher состоит из двух частей
1. Dispatcher — объект, получающий сообщения из канала и рассылающий их получателями
2. Performer — объект, получающий сообщения от Dispatcher и обрабатывающий их

Selective Consumer


Каким образом получатель может выбирать только те сообщения, в которых он заинтересован?


Используйте SelectiveConsumer — приложение, фильтрующее сообщение, доставляемые каналом, и таким образом, получая только те сообщение, в которых оно заинтересовано.

Альтернативы этому шаблону — MessageDispatcher и MessageFilter.
В случае с MessageDispatcher критерий отбора сообщений задан в диспетчере. В случае с MessageFilter, фильтрация происходит в отдельном фильтре, и получатель просто не получает отфильтрованные сообщения.

Durable Subscriber


Каким образом получатель может избежать потери сообщений в те моменты, когда он временно не слушает канал?

Т.е. в случаях, когда подписчик присоединен к PublishSubscribeChannel, но временно отключен в момент прибытия сообщения на канал.


Используйте DurableSubscriber для сохранения сообщений, которые были опубликованы тогда, когда основной подписчик был отключен.

DurableSubscriber это обычный подписчик на PublishSubscribeChannel, но он активен только тогда, когда другой подписчик (nondurable subscriber) неактивен.

Idempotent Receiver

Каким образом получатель может иметь дело с сообщениями-дубликатами?

Получатель должен быть спроектирован как IdempotentReceiver — таким образом, чтобы он мог без последствий получать одно и то же сообщение несколько раз.

Термин idempotent взят из математики и используется для описания функций, таких как f(x) = f(f(x)). Т.е. для такого получателя не имеет значение, было ли получено сообщение только один раз или несколько.

Способы реализации:
1. Явное удаление дубликатов — сохранять все обработанные сообщение и игнорировать те, которые уже были обработаны.
2. Определить семантику сообщений так, чтобы применение сообщения несколько раз вело к одному и тому же результату вне зависимости от того, сколько раз оно было применено (например, вместо «положить на счет 10$» использовать «установить остаток на счёте в 150$»).

Service Activator


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

Это может быть достигнуто с помощью применения шаблона Service Layer [EAA]


Используйте ServiceActivator, который соединяет сообщения, доставленные на канал, со службой приложения.

ServiceActivator может быть либо односторонний (one-way — только запросы), либо двусторонний (two-ways, RequestReply).

System Management

Шаблоны из этой категории имеют дело с контролем, наблюдением и анализом сообщений, тестированием и отладкой.

Control Bus


Каким образом можно эффективно администрировать распределённую систему?


Используйте ControlBus для управления системой. ControlBus использует точно такой же механизм отправки и получения сообщений, как и остальная система, но использует отдельные каналы для передачи данных.

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

1. Configuration — конфигурация компонентов может быть изменена с помощью сообщений, отправленных из ControlBus.
2. Heartbeat — периодически отправляемые сообщения для проверки того, что компонент «живой».
3. TestMessage — проверяет то, что компонент обрабатывает сообщения корректно.
4. Exception — оповещения об исключительных ситуациях в компонентах.
5. Statistics — статистические отчеты — количество обработанных сообщений, проходимость, среднее время обработки сообщения и т.п.
6. Live Console.

Detour


Каким образом можно перенаправлять сообщения через промежуточные шаги для валидации, тестирования и отладки?

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


Используйте Detour вместе с ContentBasedRouter, управляемый через ControlBus. В одном состоянии, роутер перенаправляет входящие сообщения через дополнительные этапы обработки, а в другом — отправляет сообщения напрямую в пункт назначения.

Wire Tap


Каким образом можно анализировать сообщения, поступающие на PointToPointChannel?


Используйте WireTap на инспектируемом канале. WireTap — простой RecipientList, публикующий каждое входящее сообщение как в пункт назначения, так и в дополнительный канал.

Так же может быть полезно сделать WireTap конфигурируемым через ControlBus — так, что бы дополнительный канал мог быть включен/отключен в любое время.

Message History

Каким образом можно эффективно анализировать поток сообщений в слабо связанной системе?


Присоединяйте MessageHistory к сообщению. MessageHistory — это список всех приложений или компонентов, через которые сообщение проходило со времени своего возникновения. Каждый компонент, обрабатывающий сообщение, добавляет запись в этот лист.

MessageHistory должен быть частью заголовка сообщения.

Message Store


Каким образом можно организовать составление отчетов о сообщениях без серьезного вмешательства в систему?

Для того, чтобы была возможность составлять осмысленные отсчет, необходимо иметь возможность сохранять все данные о сообщениях централизованно. MessageHistory в данном случае разбросано по всем сообщениям и не централизованно.


Используйте MessageStore для сохранения информации о каждом сообщении в централизованном хранилище.

При отправке сообщения на канал, его дубликат так же отправляется в MessageStore. Это может быть осуществлено с помощью WireTap.

Smart Proxy


Каким образом можно отслеживать сообщения для приложения, публикующего ответы на ReturnAddress, заданный отправителем? (В таком случае нельзя присоединить WireTap ко всем отправителям.)

Используйте SmartProxy для хранения ReturnAddress изначального запроса и замены его на адрес канала с SmartProxy. Когда приложение отправляет ответ, перенаправляйте его на изначальный ReturnAddress.

SmartProxy перехватывает сообщения, отправляемые на канал запросов (request channel) RequestReply компонента. Для каждого входящего сообщения, SmartProxy сохраняет ReturnAddress, обозначенный изначальным отправителем. Когда приходит ответ, SmartProxy осуществляет все необходимые аналитические задачи, извлекает сохраненный оригинальный ReturnAddress и перенаправляет ответ в оригинальный канал используя MessageRouter.

Оригинальны ReturnAddress может быть сохранён
- внутри сообщения
- внутри SmartProxy

Test Message

Что делать, если компонент обрабатывает сообщения, но есть сомнения, что он делает это правильно?

В таком случае простой Heartbeat механизм не поможет выявить ошибку.


Добавьте TestMessage в поток сообщений для проверки того, что компонент обрабатывает это сообщение правильно, и следовательно, работает корректно.

В состав шаблона входят следующие компоненты

1. TestDataGenerator — создает тестовые сообщения.
2. TestMessageInjector- отправляет тестовые сообщения в входящий поток сообщений.
3. TestMessageSeparator — отделяет результаты тестовых сообщений от всех остальных в выходящем потоке сообщений.
4. TestDataVerifier — сравнивает полученный результат с ожидаемым результатом.

Channel Purger


Каким образом можно предотвратить появление оставшихся на канале сообщений в процессе тестирования или отладки?

Из-за GuaranteedDelivery сообщения могут быть сохранены и отправлены даже если отправитель уже неактивен.


Используйте ChannelPurger для удаления нежелательных сообщений из канала.

Могут быть удалены как все сообщения из канала, так и сообщения, удовлетворяющие некоторому критерию.

Источники

http://www.enterpriseintegrationpatterns.com/ (Недоступно по состоянию на 31 июля 2012 года, зеркало в архиве от 2011.07.23)
Все шаблоны в одном документе
Gregor Hohpe and Bobby Woolf, Enterprise Integration Patterns

Далее:

Среда, 08 Авг 2012 в 10:56. Вы можете следить за комментариями к этой статье через RSS 2.0.