BizTalk: История одного проекта

[Здесь английская версия]

Бог делал мир шесть дней. Каждый день он делал что-то, что надо было интегрировать со всем остальным. Начальные требования были грандиозны, как у всех новичков, – «Пусть все системы работают вместе. Мир интегрируйся.»
[Устные предания системных интеграторов]

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

И была одна деталь. Одна маленькая, незаметная проблема. Старая система (назовем ее А, потому что она была ПЕРВАЯ) не могла быть просто выключена, а вместо нее включена новая система (назовем ее Б, потому что она была второй, но совсем не последней). У системы А есть сотни пользователей, работающих день и ночь из разных городов большой страны. Пользователи не умеют работать на Б, их надо было научить этому. Система А имеет много замечательных, сделанных домашними программистами функций, которых нет в Б. Эти функции должны быть «перемещены» в Б, и это — длинный процесс. Месяцы и месяцы программирования.

Поэтому решение было тоже коротким и простым. Давайте не будем ничего включать/выключать, пусть обе системы работают рядом все эти несколько месяцев. За это время пользователи будут переучены и все уникальные функции будут перемещены. И вся работа незаметно переместится из Б в А, после чего А будет помечена RIP.

Это автоматически означало, что обе системы, А и Б, должны работать бок о бок все эти месяцы и использовать одинаковые данные. Данные в А и в Б должны быть синхронизованны. Вот так рождаются интеграционные проекты.

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

Первый эскиз проекта

Первый эскиз выглядит примитивно. Обе системы хранят данные в SQL базах. Когда данные изменяются, операции Создать, Изменить, Удалить выполняются на данных, и сразу стартует процесс синхронизации. Естественным решением было использование триггеров на таблицах. Когда мы говорим о данных, мы имеем в виду объекты, с которыми оперируют системы, например, Заказ и/или Товар [в Заказе].

Решено было использовать Microsoft BizTalk Server  в качестве платформы для интеграции. Почему был выбран BizTalk? Это отдельная история. Пока же можно констатировать, что BizTalk оправдал надежды.

Второй эскиз проекта

Посмотрим более детально, как это работает:

  1. Пользователь создает новый объект в системе А. Это действие запускает триггер на таблице, хранящей объект. Триггер создает и отправляет сообщение «Объект создан». Это сообщение включает все атрибуты нового объекта, но мы будем обращать внимание только на его уникальный идентификатор, Id, в системе А. Обозначение для этого сообщения будет таким – Id.A. Итак, система А создает и посылает Id.A в BizTalk Server.
  2. BizTalk преобразует Id.A в формат системы Б. Эта простейшая часть проекта и я не буду в дальнейшем заострять на ней внимание. Сообщение по-прежнему обозначено Id.A, но так как оно в другом формате, оно поменяло свой цвет. BizTalk, после преобразования формата, шлет Id.A в систему Б.
  3. Система Б создает новый объект в своей базе данных. Б использует другие идентификаторы для объектов, это Id.B. Система Б сохраняет в базе пару идентификаторов, Id.A+Id.B. Б посылает сообщение Id.A+Id.B обратно в BizTalk.
  4. BizTalk шлет сообщение Id.A+Id.B в систему A.
  5. Система A сохраняет Id.A+Id.B.

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

Третий эскиЗ проекта

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

Решением было повторять весь процесс синхронизации (Sync процесс), если он не был нормально завершен. Для этого был создан дополнительный сервис. Назовем его Resync сервис.

Теперь процесс синхронизации выглядит так:

  1. Систем A создает Id.A. Id.A сохраняется на A. Id.A посылается в BizTalk.
  2. BizTalk посылает Id.A в Resync и в B. Id.A сохраняется на Resync.
  3. Система B создает Id.B. Id.A+Id.B сохраняется на B. Id.A+Id.B посылается в BizTalk.
  4. BizTalk посылает Id.A+Id.B в Resync и в A. Id.A+Id.B сохраняется в Resync.
  5. Id.A+Id.B сохраняется в B.

Мы по-прежнему храним пары Id в обоих системах, но только для быстрого доступа к Id, не для синхронизации. Для синхронизации эти Id теперь хранятся в одном главном месте — в базе данных Resync.

Resync сервис хранит записи синхронизации в таком виде:

  • Id.A
  • Id.B
  • Entity.Type
  • Operation (Create, Update, Delete)
  • IsSyncStarted (true/false)
  • IsSyncFinished (true/false0

Resync изменяет поля IsSyncStarted и IsSyncFinished на соответственном шаге процесса.

Сервис Resync реализует три метода:

  • Save (Id.A, Entity.Type, Operation)
  • Save (Id.A, Id.B, Entity.Type, Operation)
  • Resync ()

Два Save() используются для сохранения идентификаторов в базе данных. См.шаги 2 и 4 в предыдущем примере.

А что делает метод Resync()? Этот метод запускает процесс синхонизации заново, если он не был завершен нормально. Если Save() методы стартуют внутри триггеров, то есть запускаются действиями польователя, то Resync() работает, как независимый процесс. Он периодически проверяет базу данных Resync и ищет «незавершенные» записи. Найдя их, он заново запускает процесс синхронизации.  Он пытается синхронизировать объект несколько раз после чего прекращает попытки.

Еще одно важное замечание, обе системы А и Б дожны допускать дубликаты одного и того же процесса синхронизации. Например, что будет, если на шаге 3 система Б не смогла отослать сообщение Id.A+Id.B назад? Resync сервис должен запустить процесс синхронизации заново, с первого шага. При этом Id.A будет получен системай Б во второй раз. В этом случае система Б не должна воссоздавать объект заново, а должна использовать уже созданный объект и должна заново послать Id.A+Id.B назад без всяких сообщений об ошибке. Это и означает «допускать дубликаты».

ЧетверТый эскиз

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

Сначала был дурацкий вопрос. Почему нам надо делать дополнительный сервис со специальной базой данных? Может мы сможем обойтись одним BizTalk, чтобы делать все то, что делает Resync сервис?

Теперь Resync orchestration делает то же, что и Resync сервис.

Здесь изображен процесс, выполненный без ошибок. Все просто.

Resync orchestration(какого рода orchestration?) инициируется сообщением Start и завершается сообщением Finish.

А здесь изображен процесс с ошибкой.
Resync orchestration ожидает сообщения Finish определенный период времени, после чего повторяет сообщение Id.A. Сообщение Id.A может быть повторено несколько раз, после чего Resync orchestration прекращает попытки и останавливается. Resync orchestration может быть запущено заново, уже вручную, после расследования и исправления ошибки, после чего orchestration повторяет весь процесс заново:
ожидание [, повторная_посылка_Id.A [, остановка]], завершение.

Настройка

Resync orchestration повторяет сообщение Id.A со специальным флагом “Resubmitted”. Фильтр подписки (subscription filter) на Resync orchestration включает предикат
(Resubmit_Flag != “Resubmitted”).
Это означает, что только первое Sync orchestration запускает Resync orchestration. Другие, повторные Sync orchestration-ы, запущенные в попытке завершить процесс синхронизации, могут завершить Resync orchestration, но не могут запустить другие его копии.

В этом примере система Б некоторое время была недоступна. За это время Resync orchestration успело повторить Id.A и повторно запустить Sync. Систем Б ответила одним сообщением Id.A+Id.B и этим завершила Resync и Sync.

Что интересно, сообщение Id.A было отправлено дважды, а сообщение Id.A+Id.B – только один раз. Из-за этого система Б и Resync должны «допускать дубликаты» сообщений. Мы уже говорили об этом в отношении систем А и Б. Теперь то же самое требование должно выполяться для Resync.

Предположим, что система Б не была недоступна, но реагировала очень медленно на первый запрос и Resync успело повторить Id.A. Система Б ответила сообщением Id.A+Id.B не один, а два раза. Первое сообщение Id.A+Id завершило Resync. Что будет со вторым сообщением? Куда оно должно попасть? Таким образом мы имеем еще одно «внутренее» требование. Вся система синхронизации должна «допускать дубликаты» идентичных сообщений Id.A, Id.A+Id.B, Start и Finish . В BizTalk это требование выполнить легко. Я добавил “SinkExtraMessages” subscriber (подписчик «УдалятьИзбыточныеСообщения»). Это эще одно orchestration, состоящее из одного receive shape, цель которого – получать эти избыточные сообщения и ничего с ними не делать. Избыточные сообщения нашли своего подписчика и уже не «засоряют» BizTalk.

Дизайн на практике

На практике архитектура этого проекта – более сложна и интересна.

В реальности каждая система может выдать несколько Id.A почти одновременно и совершенно беспорядочно.

Может быть выдано не только одно сообщение, но серия сообщений с разными операциями. Эти операции могут зависить друг от друга. Например операция Изменить после операции Удалить означает совсем другое, чем та же Изменить, но после операции Создать.

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

Стоит сказать, что я провел немало времени, борясь с Зомби. Есть такие интересные объекты в любой из подобных систем. В BizTalk они зовутся Зомби, в других интеграционных системах, наверное, зовутся по-другому. Зомби все еще появляются, но они перестали быть проблемой. И это – другая история.

Что интесного в последнем дизайне? Дополнительное orchestration создано для того, чтобы другое orchestration стало более надежным. Почему дизайн, состоящий из двух подобных объектов, будет надежнее? Что-то в этом странное, не так ли? Synch orchestration выполняет весь обмен сообщениями между системами, там, где случаются почти все ошибки. С другой стороны Resync orchestration обменивается простейшими сообщениями только внутри BizTalk Server, где ошибки практически исключены.

Есть ли другие варианты архитектуры у данного проекта? Конечно. Например весь функционал Resync может быть реальизован внутри Sync orchestration. Этот вариант рассматривался и получил столько же голосов «За», что и предыдущий вариант.

Хотелось бы получить и от вас, ребята, предложения по улучшению дизайна.

Оставьте комментарий

Filed under BizTalk

Оставьте комментарий