Целостность данных в микросервисах или как не дать неконсистентным данным сломать сервис

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

Для примера, рассмотрим сервис аренды машин, где используется принцип database per service:

  • Микросервис geoareas владеет данными о разных географических полигонах — городах, районах: {id, geometry}
  • Микросервис tariffs в свою очередь распоряжается данными о стоимости аренды: {id, geoarea_id, price_per_minute}. Есть зависимость от сервиса geoareas.

В какой-то момент нам понадобилось удалить объект из микросервиса geoareas — вызвать endpoint:

DELETE /geoarea?id={id}.

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

Появляется неприятная зависимость: чтобы удалить геозону из geoareas нужно в момент удаления сделать запрос в tariffs, чтобы проверить — не зависит ли от нее какой тариф.

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

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

У нас же распределенная микросервисная архитектура и есть несколько вариантов решения:

1. Webhooks

Вариант подразумевает создание в микросервисе, от которого будут зависеть другие микросервисы унифицированного механизма веб-хуков.

Микросервис будет исполнять эти хуки перед тем, как изменить объект. Если хоть один хуков завершился неудачей, то действие над объектом невозможно.

Примером такого веб-хука может быть запрос в сторонний микросервис с предопределенным API.
Для нашего примера веб-хук в микросервисе geoareas выглядел бы следуюшим образом:

WEBHOOK target=”pre-delete” action=”POST tariffs/check_delete?geoarea_id={id}”

В микросервисе tariffs, соответственно, нужно реализовать endpoint /check_delete для проверки того, что зону можно удалить и микросервис tariffs «не против».

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

Таким образом, geoareas поддерживает механизм унифицированных веб-хуков, но ничего не знает про то, что за логика внутри этих хуков.

2. Reference Counting

В этом варианте мы будем следить за тем, кто использует геозоны.

Расширяем API нашего микросервиса geoareas следующими endpoints:

POST /hold?geoarea_id={id}&holder={holder}

POST /release?geoarea_id={id}&holder={holder}

Таким образом, если хотим создать новый тариф в зоне, то перед этим нужно эту зону заблокировать в сервисе geoareas, например:

POST /hold?geoarea_id=moscow&holder=tariffs_tariffmoscow1

Рядом с геозонами в микросервисе geoareas будем хранить список тех, кто «держит» эти геозоны.

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

Если тариф, держащий геозону, удаляется, то после удаления нужно отпустить блокировку, например:

POST /release?geoarea_id=moscow&holder=tariffs_tariffmoscow1