Для данной службы 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 могут не требовать одинаковую информацию о продукте, но событие должно содержать оба (если мы не разделим события на две части). Если данные отсутствуют в данном событии, код не может их использовать - если мы добавим цветовую опцию к данному Продукту, для существующих заказов в B и C, данный продукт будет бесцветным, если мы не обновим события, а затем не запустим их повторно. ,
Решение 2
Отправлять только руководство продукта в рамках мероприятия - это означает следующую структуру для order_placed
:
{
order_id: [guid],
product_id: [guid]
}
На услуги B и C информация о продукте хранится в product_id
атрибуте orders
таблицы
Информация о продукте извлекается службами B и C, когда это требуется, путем выполнения вызова API для A/product/[guid]
конечной точки.
Проблемы : это делает В и С зависимыми от А (всегда). Если схема Продукта изменяется на 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, изменения должны быть сделаны для всех сервисов, которые зависят от них (при заполнении)
Насколько я понимаю, правильным подходом было бы пойти с решением 1 и либо обновить историю событий в соответствии с определенной логикой (если каталог продуктов не изменился, и мы хотим добавить цвет для отображения, мы можем безопасно обновить историю, чтобы получить текущее состояние продуктов и заполнить недостающие данные в событиях) или учитывать отсутствие данных (если каталог продуктов изменился, и мы хотим добавить цвет для отображения, мы не можем быть уверены, что в тот момент в прошлом данный продукт был цвет или нет - мы можем предположить, что все товары в предыдущем каталоге были черными и обслуживали путем обновления событий или кода)
источник
updating event history
- В событии источников событий история событий является вашим источником правды и никогда не должен быть изменен, а только идти вперед. Если события изменяются, вы можете использовать управление версиями событий или аналогичные решения, но при воспроизведении ваших событий до определенного момента времени состояние данных должно быть таким, каким оно было в тот момент.updating event history
я имею в виду: пройти через все события, копируя их из одного потока (v1) в другой поток (v2), чтобы поддерживать согласованную схему событий.display image at the point when purchase was made
) или не может (представляющее намеренияdisplay current image as it within catalog
)Ответы:
Решение № 3 действительно близко к правильной идее.
Можно подумать об этом: каждый из B и C кэширует «локальные» копии данных, которые им нужны. Сообщения, обработанные в B (и аналогично в C), используют локально кэшированную информацию. Аналогичным образом, отчеты создаются с использованием локально кэшированной информации.
Данные копируются из источника в кеши с помощью стабильного API. B и C даже не должны использовать один и тот же API - они используют любой протокол выборки, соответствующий их потребностям. По сути, мы определяем контракт - протокол и схему сообщения - которые ограничивают поставщика и потребителя. Тогда любой потребитель по этому контракту может быть подключен к любому поставщику. Обратно несовместимые изменения требуют нового контракта.
Службы выбирают подходящую стратегию аннулирования кэша для своих нужд. Это может означать получение изменений из источника по регулярному расписанию или в ответ на уведомление о том, что что-то могло измениться, или даже «по требованию» - действуя как кэш для чтения, возвращаясь к сохраненной копии данных, когда источник недоступен
Это дает вам «автономию», в том смысле, что B и C могут продолжать приносить коммерческую выгоду, когда A временно недоступен.
Рекомендуемое чтение: Данные снаружи, Данные внутри , Pat Helland 2005.
источник
В компьютерных науках есть две сложные вещи , и одна из них - аннулирование кэша.
Решение 2 является абсолютно моей позицией по умолчанию, и вам следует рассмотреть возможность реализации кэширования, только если вы столкнетесь с одним из следующих сценариев:
Проблемы с производительностью действительно являются основной движущей силой. Существует много способов решения проблемы № 2, которые не включают кэширование, например, обеспечение высокой доступности службы А.
Кэширование значительно усложняет систему и может создавать крайние случаи, о которых трудно рассуждать, и ошибки, которые очень сложно воспроизвести. Вы также должны снизить риск предоставления устаревших данных при наличии новых данных, что может быть намного хуже с точки зрения бизнеса, чем (например) отображение сообщения «Служба А не работает - повторите попытку позже».
Из этой прекрасной статьи Уди Дахана:
Кроме того, если вам нужен запрос данных о продукте на определенный момент времени, это должно быть обработано так, как данные хранятся в базе данных продукта (например, даты начала / окончания), должны быть четко представлены в API (дата вступления в силу должна быть входом для вызова API для запроса данных).
источник
Очень сложно просто сказать, что одно решение лучше другого. Выбор одного из решений № 2 и № 3 зависит от других факторов (длительность кэша, допуск согласованности, ...)
Мои 2 цента:
Аннулирование кэша может быть трудным, но в заявлении о проблеме упоминается, что каталог продуктов изменяется редко. Этот факт делает данные продукта хорошим кандидатом на кеширование.
Решение № 1 (NOK)
Решение № 2 (ОК)
Решение № 3 (сложное, но предпочтительное)
источник
Вообще говоря, я настоятельно рекомендую использовать вариант 2 из-за временной связи между этими двумя службами (если связь между этими службами не является сверхстабильной и не очень частой). Временная связь - это то, что вы описываете
this makes B and C dependant upon A (at all times)
, и означает, что если A отключен или недоступен из B или C, B и C не могут выполнять свою функцию.Я лично считаю, что оба варианта 1 и 3 имеют ситуации, когда они являются допустимыми вариантами.
Если связь между A и B & C настолько высока, или объем данных, необходимых для участия в событии, достаточно велик, чтобы вызвать беспокойство, тогда вариант 3 является наилучшим вариантом, поскольку нагрузка на сеть намного ниже и задержка операций будет уменьшаться по мере уменьшения размера сообщения. Другие проблемы для рассмотрения здесь:
Вариант 1 - это не то, что я бы отклонил. Существует такое же количество связей, но с точки зрения разработки это должно быть легко сделать (нет необходимости в специальных действиях), а стабильность домена должна означать, что они не будут часто меняться (как я уже упоминал).
Другой вариант, который я бы предложил, это небольшое изменение до 3, которое заключается не в том, чтобы запускать процесс во время запуска, а в том, чтобы вместо этого наблюдать событие «ProductAdded и« ProductDetailsChanged »на B и C, когда есть изменение в каталоге продукта. в A. Это ускорит развертывание (и, следовательно, упростит исправление проблемы / ошибки, если вы ее обнаружите).
Редактировать 2020-03-03
У меня есть определенный порядок приоритетов при определении интеграционного подхода:
Если цена несоответствия высока (в основном данные продукта в A должны быть как можно быстрее согласованы с продуктом, кэшированным в B и C), тогда вам не избежать необходимости принять недоступность и сделать синхронный запрос (например, веб-сайт). / запрос покоя) из B & C в A для получения данных. Будь в курсе! Это по-прежнему не означает транзакционную согласованность, но сводит к минимуму окна для несогласованности. Если вы абсолютно, безусловно, должны быть немедленно последовательны, вам нужно изменить границы своих услуг. Тем не менее, я очень сильно верю, что это не должно быть проблемой. Исходя из опыта, на самом деле крайне редко, когда компания не может принять несколько секунд несогласованности, поэтому вам даже не нужно делать синхронные запросы.
Если вам нужны запросы на определенный момент времени (которые я не заметил в вашем вопросе и, следовательно, не включил выше, возможно, неправильно), затраты на поддержание этого в последующих сервисах очень высоки (вам придется дублировать внутренняя логика проецирования событий во всех нисходящих сервисах), которая делает решение ясным: вы должны оставить владение A и запрашивать Ad-hoc через веб-запрос (или аналогичный), а A должен использовать источник событий для получения всех событий, о которых вы знали в то время, чтобы проецировать в состояние и вернуть его. Я предполагаю, что это может быть вариант 2 (если я правильно понял?), Но затраты таковы, что временная связь лучше, чем стоимость обслуживания дублированных событий и логики проекции.
Если вам не нужен момент времени, и нет ясного, единственного владельца данных (который в моем первоначальном ответе я предполагал, основываясь на вашем вопросе), тогда очень разумным было бы хранить представления продукта в каждой услуге отдельно. Когда вы обновляете данные для продуктов, вы обновляете A, B и C параллельно, выполняя параллельные веб-запросы к каждому, или у вас есть командный API, который отправляет несколько команд каждому из A, B и C. B & C используют их локальная версия данных для выполнения своей работы, которая может быть или не быть устаревшей. Это не какой-либо из указанных выше вариантов (хотя его можно сделать близким к варианту 3), поскольку данные в A, B и C могут различаться, а «весь» продукта может представлять собой совокупность всех трех данных. источники.
Знание, имеет ли источник истины стабильный контракт, полезно, потому что вы можете использовать его для использования домена / внутренних событий (или событий, которые вы храните в своем источнике событий в качестве шаблона хранения в A) для интеграции между A и сервисами B и C. Если договор стабилен, вы можете интегрироваться через доменные события. Тем не менее, у вас возникает дополнительная проблема в случае частых изменений или того, что контракт сообщений достаточно велик, что делает транспортировку проблемой.
Если у вас есть явный владелец с ожидаемым стабильным контрактом, лучшим вариантом будет вариант 1; заказ будет содержать всю необходимую информацию, а затем B и C выполнят свою функцию, используя данные в событии.
Если контракт может изменяться или часто нарушаться, следуя вашему варианту 3, то есть возврат к веб-запросам на получение данных о продукте - на самом деле лучший вариант, поскольку поддерживать несколько версий гораздо проще. Таким образом, B сделает запрос на v3 продукта.
источник
ProductAdded
илиProductDetailsChanged
усложняя отслеживание изменений каталога продуктов, нам необходимо каким-то образом синхронизировать эти данные между базами данных, в случае, если события воспроизводятся, и нам необходим доступ к данным каталога из прошлого.