Посевные базы данных микросервисов - PullRequest
10 голосов
/ 28 февраля 2020

Данная услуга A (CMS), которая контролирует модель (Product, давайте предположим, что у нее есть только поля, это id, title, price) и услуги B (Shipping) и C (Emails), которые должны отображать данную модель. каким должен быть подход, чтобы синхронизировать данные модельной информации между этими службами при подходе к источнику событий? Давайте предположим, что каталог продуктов редко изменяется (но изменяется) и что есть администраторы, которые могут очень часто получать доступ к данным отправлений и электронной почты (пример функций: B: display titles of products the order contained и C: display content of email about shipping that is going to be sent). Каждый из сервисов имеет свою БД.

Решение 1

Отправка всей необходимой информации о Продукте в рамках мероприятия - это означает следующую структуру для order_placed:

{
    order_id: [guid],
    product: {
        id: [guid],
        title: 'Foo',
        price: 1000
    }
}

На службе B и C информация о продукте хранится в атрибуте product JSON в orders таблице

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

Проблемы : в зависимости от того, какую другую информацию нужно представить в B и C, объем данных в событии может возрасти. B и C могут не требовать одинаковую информацию о Продукте, но событие должно содержать оба (если мы не разделим события на две части). Если данные отсутствуют в данном событии, код не может их использовать - если мы добавим опцию color к данному Продукту, для существующих заказов в B и C, данный продукт будет бесцветным, если только мы обновляем события, а затем повторно запускаем их.

Решение 2

Отправлять только идентификатор продукта в рамках события - это означает следующую структуру для order_placed:

{
    order_id: [guid],
    product_id: [guid]
}

В службах B и C информация о продукте хранится в атрибуте product_id в orders таблице

Информация о продукте извлекается службами B и C, когда это требуется при выполнении вызова API до A/product/[guid] конечная точка

Проблемы : это делает B и C зависимыми от A (всегда). Если схема Продукта изменяется на A, изменения должны быть сделаны для всех служб, которые зависят от них (внезапно)

Решение 3

Отправлять только руководство продукта в рамках события - это означает следующую структуру для order_placed:

{
    order_id: [guid],
    product_id: [guid]
}

Об услугах B и C Информация о продукте хранится в таблице products; по-прежнему product_id в таблице orders, но есть репликация products данных между A, B и C; B и C могут содержать информацию о продукте, отличную от A

Информация о продукте отображается при создании служб B и C и обновляется всякий раз, когда информация о продуктах изменяется, путем вызова конечной точки A/product (которая отображает требуемую информацию обо всех продуктах) или путем прямого доступа к базе данных A и копирования необходимой информации о продукте, необходимой для данной услуги.

Проблемы : это делает B и C зависимыми от A (при посеве). Если схема Продукта изменяется на A, изменения должны быть сделаны для всех служб, которые зависят от них (при заполнении)


Насколько я понимаю, правильный подход был бы к go с решением 1, и либо обновлять историю событий для определенных логи c (если каталог товаров не изменился, и мы хотим добавить цвет для отображения, мы можем безопасно обновить историю, чтобы получить текущее состояние товаров и заполнить недостающие данные в событиях) или обслуживать из-за отсутствия данных (если каталог продуктов изменился и мы хотим добавить цвет для отображения, мы не можем быть уверены, что в данный момент в прошлом данный продукт имел цвет или нет - мы можем предположить, что все продукты в предыдущем каталоге были черные и обслуживали путем обновления событий или кода)

Ответы [ 4 ]

3 голосов
/ 02 марта 2020

Решение № 3 действительно близко к правильной идее.

Можно подумать об этом: каждая из B и C кэширует"локальные" копии данных, которые они нужно. Сообщения, обработанные в B (и аналогично C), используют локально кэшированную информацию. Аналогичным образом, отчеты создаются с использованием локально кэшированной информации.

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

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

Это дает вам «автономию», в том смысле, что B и C могут продолжать приносить коммерческую выгоду, когда A. временно недоступен.

Рекомендуемое чтение: Данные снаружи, Данные внутри , Пэт Хелланд, 2005 г.

2 голосов
/ 07 марта 2020

Очень сложно просто сказать, что одно решение лучше другого. Выбор одного из решений № 2 и № 3 зависит от других факторов (длительность кэша, допуск согласованности, ...)

Мои 2 цента:

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

Решение № 1 (NOK)

  • Данные дублируются в нескольких системах

Решение № 2 (ОК)

  • Обеспечивает сильную согласованность
  • Работает только в том случае, если обслуживание продукта является высокодоступным и обеспечивает хорошую производительность
  • Если служба электронной почты готовит сводку (с большим количеством продуктов), то общее время ответа может быть больше

Решение № 3 (сложное, но предпочтительное)

  • Предпочитают подход API вместо прямого доступа к БД для получения информации о продукте
  • Устойчивость - на сервисы-потребители не влияют, когда обслуживание продукта не работает
  • Использование приложений (службы доставки и рассылки) получают сведения о продукте сразу после того, как событие опубликовано. Возможность обслуживания продукта в течение этих нескольких миллисекунд очень мала.
2 голосов
/ 05 марта 2020

Есть две сложные вещи в области компьютерных наук, и одна из них - аннулирование кэша.

Решение 2 - это абсолютно моя позиция по умолчанию, и вам следует рассматривать кеширование только в том случае, если вы запустить один из следующих сценариев ios:

  1. Вызов API для Сервиса A вызывает проблемы с производительностью.
  2. Стоимость Сервиса A снижается и не может получить данные важны для бизнеса.

Проблемы с производительностью действительно являются основной движущей силой. Существует множество способов решения проблемы № 2, в которых не используется кэширование, например обеспечение высокой доступности службы А.

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

Из этой превосходной статьи Уди Дахана:

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

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

1 голос
/ 02 марта 2020

Вообще говоря, я настоятельно рекомендую использовать вариант 2 из-за временной связи между этими двумя службами (если связь между этими службами не является сверхстабильной и не очень частой). Временная связь - это то, что вы описываете как this makes B and C dependant upon A (at all times), и это означает, что если A выключен или недоступен из B или C, B и C не могут выполнять свою функцию.

Я лично считаю, что оба варианта 1 и 3 имеют ситуации, когда они являются допустимыми вариантами.

Если связь между A и B & C настолько высока, или объем данных, необходимых для go в событие, достаточно велик, чтобы сделать его беспокойство, тогда вариант 3 является лучшим вариантом, потому что нагрузка на сеть намного ниже, и задержка операций будет уменьшаться с уменьшением размера сообщения. Другие проблемы, которые следует рассмотреть здесь:

  1. Стабильность контракта: если контракт сообщения, покидающего A, часто меняется, то добавление большого количества свойств в сообщение приведет к большим изменениям у потребителей. Однако в этом случае я считаю, что это не будет большой проблемой, потому что:
    1. Вы упомянули, что система A является CMS. Это означает, что вы работаете со стабильным доменом, и поэтому я не думаю, что вы будете часто сталкиваться с изменениями
    2. Поскольку B и C отправляют и отправляют электронную почту, а вы получаете данные от А, я полагаю, что вы будете испытывать аддитивные изменения, а не ломать их, которые можно добавлять всякий раз, когда вы обнаружите их без каких-либо переделок.
  2. Соединение: очень мало или нет сцепление здесь. Во-первых, поскольку связь осуществляется посредством сообщений, во время заполнения данных не существует никакой связи между службами, кроме коротких временных, и договором об этой операции (который не является связью, которую вы можете или должны попытаться избежать)

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

Другой вариант, который я бы предложил, это небольшое изменение до 3, которое заключается не в том, чтобы запускать процесс во время запуска, а в том, чтобы вместо этого наблюдать событие «ProductAdded» и «ProductDetailsChanged» на B и C, когда есть изменение в каталоге продуктов в A. Это ускорит развертывание (и, следовательно, упростит исправление проблемы / ошибки, если вы ее обнаружите).


Редактировать 2020-03-03

У меня есть определенный c порядок приоритетов при определении подхода к интеграции:

  1. Какова стоимость согласованности? Можем ли мы принять несколько миллисекунд несоответствия между вещами, измененными в А и они отражаются в B & C?
  2. Вам нужны запросы на определенный момент времени (также называемые временными запросами)?
  3. Есть ли источник истины для данные? Служба, которая владеет ими и считается восходящей?
  4. Если есть владелец / единственный источник правды, это стабильно? Или мы ожидаем увидеть частые критические изменения?

Если цена несоответствия высока (в основном данные продукта в A должны быть как можно скорее согласованы с продуктом, кэшированным в B и C), тогда вы не сможете избежать необходимости принять недоступность и сделать синхронный запрос (например, запрос web / rest) от B & C к A для получения данных. Будь в курсе! Это по-прежнему не означает транзакционную согласованность, а сводит к минимуму windows за несогласованность. Если вы абсолютно, безусловно, должны быть последовательными, вам нужно изменить границы своих услуг. Однако я очень твердо верю, что это не должно быть проблемой. Исходя из опыта, на самом деле крайне редко, когда компания не может принять несколько секунд несогласованности, поэтому вам даже не нужно делать синхронные запросы.

Если вам нужны запросы на определенный момент времени (которые я не заметил в вашем вопросе и, следовательно, не упомянул выше, возможно, неправильно), стоимость поддержки этого в последующих сервисах очень высока (вы бы необходимо продублировать внутреннюю логику проекции события c во всех последующих службах), что делает решение ясным: вы должны оставить владение A и запросить A ad-ho c через веб-запрос (или аналогичный), а A должен использовать событие источник, чтобы получить все события, о которых вы знали в то время, чтобы спроецировать в состояние и вернуть его. Я предполагаю, что это может быть вариант 2 (если я правильно понял?), Но затраты таковы, что, хотя временная связь лучше, чем стоимость обслуживания дублированных событий и логических проекций c.

Если вы этого не сделаете нужен момент времени, и нет четкого, единого владельца данных (который в моем первоначальном ответе я предположил, что это основано на вашем вопросе), тогда вполне разумным было бы хранить представления продукта в каждом сервис отдельно. Когда вы обновляете данные для продуктов, вы обновляете A, B и C параллельно, делая параллельные веб-запросы к каждому, или у вас есть командный API, который отправляет несколько команд каждому из A, B и C. B & C используют свою локальную версию данных для выполнения своей работы, которая может быть или не быть устаревшей. Это не какой-либо из вышеперечисленных вариантов (хотя его можно сделать близким к варианту 3), поскольку данные в A, B и C могут различаться, и «весь» продукта может представлять собой совокупность всех три источника данных.

Полезно знать, что источник истины имеет стабильный контракт, потому что вы можете использовать его для использования домена / внутренних событий (или событий, которые вы сохраняете в своем источнике событий как шаблон хранения в A) для интеграции между А и службами B и C. Если договор стабилен, вы можете интегрироваться через доменные события. Тем не менее, у вас возникает дополнительная проблема в случае частых изменений или того, что договор сообщения достаточно велик, что делает транспортировку проблемой.

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

Если контракт может изменяться или часто нарушаться, следуя вашему варианту 3, то есть Откат к веб-запросам для получения данных о продуктах на самом деле является лучшим вариантом, поскольку поддерживать несколько версий гораздо проще. Поэтому B сделает запрос на v3 продукта.

...