Как стать автором
Обновить
56
0
Ilya Kaznacheev @Color

Consulting Cloud Architect, GDE on Cloud

Отправить сообщение

А каковы масштабы у вас? Сколько сервисов и сколько событий происходит в рамках среднего процесса?

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

Я не пурист, поэтому предпочитаю использовать существующие практики и адаптировать их к конкретным задачам, нежели стремиться строго советовать постулатам (очень общим и абстрактным в конкретном случае), выдвинутым довольно давно и уже обросшими своими практиками. DDD, как мы знаем, тоже не только Big Blue Book, а имеет множество интерпретаций, в том числе противоречивых. Грегу привет.

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

Через gRPC происходит чтение данных моделей других сервисов (доменов). Я не знаю, какую альтернативу вы тут видите - агрегация всех данных в каком-то data lake и чтение оттуда, либо переход на event-sourcing и построение локального view каждой модели внутри сервиса?

Мы не используем event-sourcing, у нас более классический подход, основанный на CQRS, где запросы выполняются синхронно по gRPC, а команды асинхронно через сообщения. Где вы в такой модели видите размытия границ, я не вижу, при том, что запросы на чтение ничего не изменяют (а значит, никакого bounded contexts по факту там нет, да и доменов нет, потому что для запросов в CQRS отдельные упрощенные и оптимизированные для скорости чтения и кеширования модели), а запросы на изменение (команды) четко ограничены обработкой внутри домена, и никаких открытых соединений с другими сервисами в процессе этого нет, только чтение из брокера и запись в брокер..

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

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

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

Мы пишем на go, поэтому FSM решили реализовать в духе go-way - максимально просто и читабельно. В итоге каждый FSM это обычный switch вида

	switch state {
	case model.InitialState:
		switch event {
		case eventNodeChanged:
			// ...

		case eventNodeDeleted:
			// ...
		}

	case model.NodeInstanceCreationPending:
		switch event {
		case eventNodeChanged:
			// ...

		case eventNodeDeleted:
			// ...
		}

	case ...
	}

Да, в отдельных случаях такой свич разрастается до внушительных размеров, и его становится не очень удобно читать. Но при этом такой код понятен "без дебаггера" и прост в доработке и траблшутинге. Изначально тоже пробовал разные библиотеки, думал над табличной или иной динамической реализацией, но здравый смысл в итоге победил, и самое простое решение оказалось самым простым. Более того, код в принципе понятен и тем, кто не сильно знаком с концепцией конечных автоматов, потому что он (код) прост и вполне линееню

Заключение

Чтобы ответить на этот вопрос, мне бы хотелось сначала понять - а как вы решаете эту проблему, и в чем состоит та реализация саги, про которую вы говорите. Можно со ссылками и так далее.

От меня

То, что вы выполняете запросы через p2p протокол между вашими подами - это не есть хорошо. Это означает, что у вас (с большой долей вероятности) неправильно проведены границы между агрегатами и bounded contexts, что у вас высокий уровень зависимости компонентов (temporal and spatial). Вам надо глубже погрузиться в SOA, EDA и уже потом накладывать DDD и иногда добавлять CQRS (но только иногда и в основном для UI), чтобы избавиться от p2p взаимодействия.

А что вы собственно подразумеваете под p2p протоколом и какую альтернативу предлагаете?

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

Доменная логика «не вытекает» из домена, а доменные транзакции независимы

Ну, вообще-то, в этом вся концепция домена и агрегата в DDD - оттуда ничего и никогда не "вытекает", потому как это чистейшая абстракция бизнес требований и процессов. Модель создаётся из сохранённого состояния и исполняется для того, чтобы получить новое состояние модели, которое потом и сохраняется с использованием репозитории.

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

DDD

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

Если кратко - сложность есть, и она довольно большая, но статья сильно не про это (а еще есть NDA), поэтому я очень сильно упрощал в статье и сами модели, и примеры FSM. Ради примера количество возможных состояний одного из доменов в агрегате больше сорока, таких доменов больше полдюжины. Изначально мы решали проблему того, что логики очень много и она сложная, и как раз DDD (и гексагон как практическая реализация на уровне архитектуры) помогли эту проблему решить.

CQRS

Не очень понятно, почему вы делаете такой упор на CQRS в этой статье? В принципе этот аспект совершенно не интересен в этом процессе. Для чего существует CQRS? Только для того, чтобы упростить канал чтения и снизить нагрузку на систему - нет никакого смысла загружать полную модель (под)системы с кучей сложных бизнес объектов только для того, чтобы отобразить что-то на экране или ответить на какой-то заранее известный запрос. Концепция CQRS создавалась Грегом Янгом именно исходя из этих требований. Наложения SOA там не было никогда (это к вопросу о командах).

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

Про CQRS и его разные реализации я рассказывал здесь, добавлю только, что я не считаю определения Янга в его статье каким-то единственно верным или правильным, подход применяется на различных уровнях и включает большое количество техник вокруг, часть из которых мы используем.

Хорошие вопросы, попробую ответить на каждый

«Ой да сагу добавим, и все заработает». В действительности это не так.

Да нет, в действительности это именно так и есть. Описанное здесь, это ни что иное, как сага, потому как Сага это именно воплощение распределённой во времени бизнес транзакции. Сага неминуема в высоко распределённых системах, если нужно выполнить действия в разных компонентах связанные друг с другом для достижения общего и конечного результата. Непонятно почему автор продолжает рассказывать о "распределённой транзакции" и почему отбрыкивается от концепции Саги. Хотелось бы узнать.

Может быть у вас какие-то особые саги, поделитесь. Но все, что я видел (и в книгах, и к сожалению вживую) это

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

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

Второй вариант с событиями и всем таким - это реактивный подход, когда каждый сервис реагирует на определенное событие каким-то действием. Можно конечно вместо событийной модели построить командную (message-based), но тогда возникает риск либо прийти к оркестратору (который будет рассылать команды), либо к протеканию одного сервиса в другой, и так или иначе я вижу event-based обычно в связке с хореографической сагой.

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

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

  1. строим логику в более простом императивном формате, то есть публичный контракт сервиса и его реализация, вместо реакций на события

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

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

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

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

Пояснительная бригада прибыла.

как именно выполнялись ролбеки

У каждого домена для каждого переходного состояния (то есть когда какой-то процесс происходит над сущностью) есть обработка "хорошего" и "плохого" сценария. Например, при создании ВМ в состоянии, когда мы ждем запуска ОС, в "хорошем" сценарии (при ответе, что ОС запущена), мы перейдем в следующее состояние, попутно затригеррив соответствующее действие. В "плохом" сценарии (при ответе, что произошла ошибка), мы переходим в соответствующее началу роллбека состояние, попутно запустив удаление ВМ. Для каждого отдельно взятого состояния конкретное действие будет разным, потому что в разных состояниях может понадобится выполнять разные действия для роллбека, но процесс роллбека запускается в любом случае, и проходит через все состояния, чтобы откатить/удалить сущность.

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

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

как обеспечивалась работа параллельных транзакций

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

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

В статье речь про ОС, для запуска контейнерного рантайма (т.е. ОС для оркестратора).

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

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

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

Вы снова занимаетесь подменой тезисов. Я где-то говорил про "от зубов отлетать"?

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

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

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

Напишу сюда универсальный ответ на все подобные вопросы.

В ТРИЗ есть тезис о том, что при решении задачи нужно уметь подняться на один уровень выше и опуститься на один уровень ниже.
Аналогично и в разработке - при написании, например, веб сервиса, нужно уметь опуститься ниже (как работает ЯП под капотом, как оптимизировать циклы, как устроена работа с памятью (в первом приближении) и т.п.), и подняться выше (как взаимодействуют сервисы, как работают реплики сервиса и как избежать конфликтов, как происходит выкатка, и как сделать ее отказоустойчивой). Это все должен знать опытный разработчик, чтобы делать оптимальный дизайн приложения, хотя может напрямую аллоцировать память или писать blue-green деплой ему/ей не придется.

Например, понимание canary деплоймента важно при работе с базой данных - у вас в один момент могут быть развернуты реплики двух разных версий, соответственно модель БД должна уметь работать и со старой версией приложения, и с новой одновременно.

Другой пример - знание, что у вас развернут Istio (и что в нем есть retry и circuit breaker) позволит не велосипедить эти паттерны в приложении.

Обобщая, разработчику может и не будет нужно работать с кубом, особенно если в команде есть девопс-инженеры. Но знать примитивы и подходы очень полезно каждому, кто работает в такой среде, чтобы писать хорошие с точки зрения дизайна и отказоустойчивости приложения. Как и в случае с безопасностью, хороший "cloud-native" дизайн нельзя "прикрутить" потом - это нужно делать с самого начала разработки приложения (если речь не про PoC/MVP, где законно накостылить как получится). А для этого нужно иметь понимание обо всем окружении, с которым и в котором работает приложение, пусть и не быть в этом экспертом.

Перечитайте пожалуйста название статьи. В нем ответ на ваше замечание.

Если вам нужен Куб (а как по мне лично - то и докер) - возьмите человека и не делайте мозг программистам

В статье же написано "разработчик", а не "программист". Если кратко: программист это про программирование, разработчик же знает процесс разработки, в который входит и программирование, как один из инструментов. А еще, например, траблшутинг, умение понять задачи, аналитика и пр.

Во-вторых, не все работают на галерах, где гребцу дают "весло" (фреймворк) и куда грести (тикет с описанием функции). Встречал таких, кто 15 лет опыта имея за плечами, знают только как писать код, затрудняясь рассказать про работу с БД или про сетевые протоколы. Многие же приходят в продуктовые команды и на более интересные проекты, где нет "проторенной дорожки", и много RnD, и разработчик и код пишет, и контейнеры делает, и может в куб зайти и посмотреть, что там сломалось с его приложением.

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

Ну и в-четвертых, если вы придете в стартап на этапе, когда там меньше 10 человек, вы как раз будете тем Васей, который умеет все понемножку, и задеплоить в кубер может быть нужно в том числе.

1
23 ...

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность