A Pattern Language for Service-based Platform Integration and Adaptation

Ioanna Lytra, Stefan Sobernig, Huy Tran, and Uwe Zdun. 2012. A pattern language for service-based platform integration and adaptation. In Proceedings of the 17th European Conference on Pattern Languages of Programs (EuroPLoP '12). ACM, New York, NY, USA, , Article 4 , 27 pages. DOI=http://dx.doi.org/10.1145/2602928.2603080

Программная платформа - совокупность программных подсистем, которые все вместе формируют переиспользуtмую инфраструктуру для разработки приложений. Для сборки конкретного приложения с переиспользованием артефактов платформы выполняется кастомизация и конфигурирование одного из интерфейсов.

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

Язык шаблонов

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

Интеграция и адаптация

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

  • Паттерн Proxy. Есть ситуация, в которой клиент не может или не имеет права общаться напрямую с сервисом. Решение: добавляется промежуточный объект с таким же интерфейсом как у сервиса и имеющий ссылку на сервис. Все запросы клиента proxy направляет к сервису. Можно добавить дополнительную функциональность к proxy, не изменяя сервис.
  • Remote Proxy. Cитуация как в Proxy, с дополнительным ограничением: клиент и сервис находятся в разных процессах или машинах. Решение: добавляется объект, которые может соединять объекты на разных машинах или процессах.
  • Object Adapter. Клиент и сервис имеют не совпадающие интерфейсы. Решение: дополнительный объект, который конвертирует интерфейс сервиса в необходимый.
  • Integration adapter. Не желательно, чтобы изменение в интерфейсах позволило вызовам и сообщениям теряться. Решение: Object Adapter, реализующий интерес паттерна Component configurator, c двумя коннекторами(для клиента и сервиса).
  • Component configurator. Хотим иметь возможность настраивать(запускать, приостанавливать, завершать) компоненты в любое время жизни приложения так чтобы это не влияло на остальные части приложения. Интерфейсы компонент отделяются от реализации и используются для контроля компонент. Реализации компонент реализуют поведение, необходимое приложению.
  • Plug-in Хотим поддерживать различные протоколы в распределенном приложении. Решение: protocol plug-in содержит общие интерфейсы для различных протоколов. Для конфигурации параметров протоколов, protocol plug-in предоставляет api или файл конфигурации. Таким образом поддерживается настройка и оптимизация используемых протоколов.

Дизайн интерфейса

  • Data transfer object. При увеличении числа запросов передача данных между двумя распределенными приложениями становится очень дорогой. Решение: Data transfer object содержит все данные, которые должны быть переданы и пересылает всю информацию за один вызов.
  • Facade. Для приложения-клиента нужны стабильные интерфейсы различных версий компонент. Решение: Facade - объект или компонента, которая предоставляет общий и почти наверное упрощенный интерфейс для множества программных компонент.
  • Remote facade. Использование маленьких объектов с маленькими методами ведет к большему пониманию клиентом работы приложения и больше возможностей для контроля и изменения поведения. Последствие «мелко-молотого“ поведения в том, что обычно происходит много взаимодействий между объектами с вызовом множества методов. Решение: Remote Facade сопоставляет «мелко-молотым» объектам «крупно-молотые» объекты. Все количество вызовов get и set заменяются на один get и set. Таким образом улучшается производительность, однако требует большего количества кода.
  • Gateway. Сложные интерфейсы приводят к усложненным приложениям. Gateway - обертка, которая переводит сложный интерфейс в более простой. Приложения вызывают api, предлагаемое gateway. Это позволяет упростить тестирование и делает изменения ресурса более гибкими(при изменениях api источника требуется изменить только gateway).
  • Service abstraction layer. Система или платформа должна иметь возможность предоставлять и использовать удаленные объекты через разные каналы передачи. Такая поддержка каналов должна не зависеть от общей обработки вызовов удаленными объектами. Новые каналы должны добавляться в любой момент. Решение: service abstraction layer добавляет дополнительный слой, который получает и перенаправляет запросы поступающие с разных каналов. Каждый канал имеет адаптер, который переводит запросы между форматами каналов на фронтенде и бэкенде. Таким образом отделяется бизнес логика от логики общения, изменения в бизнес-логике не влияют на реализацию клиента и клиенты используют стабильные интерфейсы взаимодействия с удаленной системой.
  • Extension interface. Интерфейс компоненты может адаптироваться и расширяться. Также разным клиентам нужны разные интерфейсы компоненты. Решение: связанная функциональность группируется в Extension interfaces. Общая и/или административная функциональность(например, выбор версии) включается в корневой интерфейс и в каждый из extension interfaces. Таким образом клиенты связанны с компонентой только той функциональностью, которую используют и не ломаются при добавлении новых сервисов к компоненте.

Стиль коммуникации

  • Remote procedure invocation. Нужно предоставить возможность обмениваться данными и процессами между приложениями на разных языках программирования и запускаемыми на разных платформах. Решение: каждые приложение предоставляет удаленный интерфейс для взаимодействия с другими приложениями. Одно приложение может получить или изменить данные из другого приложения, вызывая его удаленный интерфейс. Системы, базирующиеся на remote procedure invocation, более ненадежные чем, например, базирующиеся на messaging, так как во время поломки системы или управления системой входящие вызовы будут потеряны.
  • Fire and forget. Клиентское приложение хочет уведомить удаленный объект о каком-то событии. При этом не нужен  результат или гарантия доставки сообщения. Решение: Операция fire and forget реализуется передачей сообщения без подтверждения клиенту обработки или статуса доставки. Нить управления возвращается клиенту незамедлительно. Данные паттерн обеспечивает неблокирующее общение с ненадежной передачей.
  • Sync with server. Клиентскому приложению требуется более высокая надежность асинхронных вызовов чем в fire and forget, но не обязательно передача результата. Решение: клиент отправляет вызов как в fire and forget, но ждет ответа от сервера об успешной передаче вызова. Средство общения заблокировано только момента успешной доставки вызова. Паттерн обеспечивает успешную передачу запросов и делает удаленные вызовы надежнее чем fire and forget. Однако появляется дополнительная задержка, так как клиент блокируется до получения уведомления об успешной доставки вызова.  Также серверное приложение не может информировать клиента об ошибках, так как запуск удаленного вызова происходит асинхронно.
  • Pool object. Удаленные вызовы от клиента должны быть обработаны асинхронно, однако клиенту нужен результат. Решение: pool object получает результаты от удаленных вызовов как представитель клиента. Клиент периодически запрашивает результаты у pool object. Когда результаты появляются в pool object, клиент их забирает. Паттерн предлагает более надежное общение по сравнению с fire and forget, однако клиент узнает не сразу о результатах. 
  • Result callback. Клиенту нужны результаты асинхронных вызовов незамедлительно при их доступности. Решение: клиент отправляет объект сallback во время удаленного вызова. После вызова клиент может продолжить с другими задачами. Когда вызов завершается и результаты становятся доступны, callback вызывается на клиенты для обработки результата. Паттерн более предпочтителен, чем pool object когда требуется незамедлительная реакция на результат вызова.
  • Messaging. Приложения, разрабатываемые независимо, на разных языках, запускаемые на разных платформах, должны делиться данными и процессами с получением ответа. Решение: Сообщения используются для передачи пактов данных часто, независимо, надежно и асинхронно, используя кастомные форматы. Очереди сообщений могут временно хранить сообщения, когда системы не работают, поэтому системы менее зависят друг от друг чем в remote procefure invocation. В то же время увеличивается сложность разработки, так как необходимо продумать форматы сообщений, передачу сообщений, связь приложений с системой сообщений и прочее.
  • Publish-subscriber. Данные изменяются в одном месте, но многие компоненты зависят от этих данных и должны быть изменены. Нужно уведомить много компонент об изменениях в одной компоненте. Решение: одна компонента становится распространителем, остальные компоненты - подписчики. Объект может быть подписчиком многих распространителей и быть одновременно подписчиком и распространителем. Данный паттерн позволяет слушать события, не мешая потоку общения. Поэтому это может использоваться для дебага и логгирования. Однако могут возникнуть проблемы с безопасностью, так как любой подписчик может видеть события, генерируемые распространителем. Также не гарантируется доставка событий к подписчикам.
  • Request-reply. Два приложения общаются посредством обмена сообщениями. Отправляющее приложение хочет получить ответ на отправленное сообщение. Решение: сообщение-ответ отправляется посредством обратного канала запроса или посредством собственного канала общения. Данный паттерн обеспечивает двустороннюю передачу сообщений по двухстороннему каналу. Источник запроса всегда уведомляется об успешном или неуспешном завершении и/или о результате запроса. Два процесса(запрос и ответ) не связанны. Если соединение между отправителем и получателем ломается до отправки ответа, отправитель должен переотправить запрос. 
  • Request-acknowledge. Клиент хочет уведомлять систему о том что запрос пришел или об интересном событии после того как запрос пришел. Запросы не обязательно должны выполняться незамедлительно и ответы на запросы не обязательно должны приходить. Решение: Когда сервис получает запрос, он перенаправляет запрос на фоновый процесс и затем возвращает уведомление, содержащее уникальный идентификатор запроса. Данный паттерн уменьшает проблему недоступных ресурсов. Однако клент не информируется об ошибках приложения, которые могут возникнуть во время запуска процесса.

Поток коммуникации

  • Message router. Канал сообщений может быть использован для того чтобы обмениваться сообщениями разной структуры и содержания, поэтому необходима разная обработка. Нужно отфильтровать сообщения в зависимости от разных условий. Решение: компонента Message router вставляет специальный фильтр, который принимает message с одного входящего message channel и пересылает его на другой выходной message channel, после проверки необходимых критериев. Таким образом централизуются фильтры сообщений в одну компоненту, которая становится единой точкой управления и падений. В то же время централизация сообщений добавляет накладные расходы на обработку.
  • Content-based router. Интеграционное решение развертывает message router для того чтобы обрабатывать сообщения адекватно. Однако маршрутизация сообщений должна определяться не внешними факторами или фиксированными маршрутами, а по содержанию сообщений. Решение: Content-based router изучает содержание сообщения и распространяет сообщение другой канал на основании его содержимого(например, данные о маршрутизации, структура сообщения, значение сообщения).
  • Splitter. Сообщения, проходящие через интеграционное решение, состоят из нескольких элементов, каждый из которых должен быть обработан отдельно. Входящее сообщение появляется в виде составного сообщения. Решение: компонента Splitter встраивается в платформу интеграции, чтобы разбивать составное сообщение на ряд отдельных элементов или элементов подмножеств. Каждое подмножество затем распространяется как отдельное сообщение. Общие элементы исходного сообщения сохраняются в полученных сообщениях (например, знаки идентификации и последовательности) для того, чтобы позволить повторно собрать ответные сообщения в дальнейшем. Разбитие большого сообщения упрощает дальнейшую обработку. Недостатком splitter является множественное повторение данных в итоговых сообщениях и необходимость дополнительной обработки для объединения ответных сообщений.
  • Aggregator. Нужно собрать данные с разных, но связанных сообщений, чтобы можно было обработать далее все данные вместе. Решение: компонента aggregator наблюдает поток сообщений, собирает и хранит сообщения, основываюсь на критериях фильтров и знаках идентификации(correlation identifiers), пока не соберет полный набор связанных сообщений. После того, как будет собрано одно сообщение из этих частей, используя конкретные стратегии сборки, получившееся сообщение отправляется к целевой системе. Недостатком является появление дополнительных усилий на разработку и дополнительная сложность обработки. Однако агрегация сообщений позволяет реализовать кусочную обработку сервисных вызовов.
  • Content filter. Отправляя сообщения с одной системы на другую, часто встречаются ситуации, в которых принимающая система не заинтересована во всех данных, включенных в пересылаемые сообщения. Решение: компонента content filter обеспечивает удаление ненужных, устаревших или защищенных данных из сообщений.
  • Content enricher. Отправляя сообщения с одной системы на другую, часто встречаются ситуации, в которых целевой системе нужно больше данных, чем в исходном сообщении. Решение: специальный преобразователь сообщений content enricher получает данные, внешние к системе обработки сообщений, чтобы добавить пропущенные сообщения.
  • Data mapper. Две или более компоненты обмениваются данными, находящимися в памяти. Однако для получающих интерфейсов необходим входной формат данных, несовместимый с структурой обмениваемых данных. Решение: включить дополнительный преобразующий слой в архитектуре компоненты, который содержит группу компонент-мапперов. Эти data mapper-ы обеспечивают трансформацию объектов данных. Можно использовать proxy, если общение происходит между процессами.
  • Message translator. Два или более взаимодействующих приложения используют разные форматы сообщений и возникает несоответствие форматов. Решение: message router использует компоненту-фильтр, которая преобразует формат входящего сообщения в формат выходного сообщения. Один message translator может покрывать несколько разных уровней преобразования(модели, типа, представления и траспортировки). Компонента message translator помогает избежать использования одинаково формата сообщения во всех клиентских приложениях и сервисах. Он сохраняет независимость интегрирующих клиентов и сервисов в терминах форматов сообщений.
  • Correlation identifier. Используя асинхронные удаленные вызовы или сообщения, вызывающая компонента не блокируется, даже если ответ является частью асинхронного request-reply разговора. Однако нужно уметь сопоставлять ответ запросу. Решение: чтобы связать два сообщения, запрос и ответ, обоим сообщениям присваивается уникальный знак идентификации. В сообщении-запросе correlation identifier-ом является id запроса. Сообщение-ответ имеет correlation identifier, который соответствует или ссылается на id запроса.

На рисунке ниже представлена часть архитектуры интеграции, содержащей три платформы на бэкенде(Yard Management System YMS, Warehouse Management System WMS, Remote Maintenance System RMS), Virtual Service Platform (VSP) и приложение.

Два типичных дизайна потока общения между приложением и тремя платформами:

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