Все тестирование проходит и в ci/cd и локально. Т.е. в ci/cd у нас есть отдельно запуск юнит тестов и отдельно запуск компонентных тестов.
Как выглядят компонентные тесты:
Это отдельный подпроект внутри проекта с сервисом. Там находятся собственно код тестовых сценариев, compose-файлы с помощью которых поднимается окружение необходимо для работы тестируемого сервиса( kafka, postgres и т.д.) и собственно сам сервис.
Режим работы с тестами локально. Поднимаются с помощью gradle таски все контейнеры, необходимы для работы сервиса. Сам сервис можно просто запустить из IDEA или собрать контейнер и также запустить в контейнере с помощью конфигурации в compose файле. Далее запускаются сам код тестов. Если смысл работы сервиса условно заключается в том, что надо вычитать сообщение из топика кафки, обогатить его, послав запрос в другой сервис по ресту и далее положить его в кафку, то тест будет собственно выполнять это: 1) сформируем сообщение в кафку 2) настроим wiremock для эмуляции ответа от внешнего сервиса 3) отправим сообщение в кафку 4) проверим, что в результирующем топике появилось соответствующее сообщение
Режим работы тестов в ci/cd. Все тоже самое с той лишь разницей, что сначала собирается артефакт docker container с сервисом, далее с помощью gradle таски поднимается окружение( kafka, postgres и т.д.),далее поднимается сам контейнер и потом осуществляется запуск компонентных тестов с помощью gradle test из проекта с автотестами.
В целом автотесты вполне себе легко запускаются и локально и в ci cd, у нас нет ничего специализированного для ci cd, скорей наоборот на виртуалках ci cd обычно ресурсов меньше, чем локально.
E2E тесты у нас немного вынесены за процесс сборки артефактов сервисов и прогоняются также в том числе локально и в ci cd уже непосредственно перед релизом.
Cкептически отношусь к использованию @SpringBootTest. В прошлом встречал применения коллегами таких тестов совместно с TestContainers, и у них есть ряд существенных недостатков, кроме собственно того, что описано в статье: 1. Тесты получаются излишне запутанными по структуре. 2. Запуск занимает много времени, что замедляет цикл разработки. Хочется иметь возможность тесты гонять не только в CI/CD. 3. Трудная диагностика сбоев. При падении теста разобраться в причине как правило крайне сложно, зависит конечно от автора, но у меня лично глаз начинает дергаться, если вижу, что упал «SpringBootTest», не видел «понятных» по структуре: - в логах перемешаны сообщения от TestContainers, тестового фреймворка и бизнес‑логики; часть компонентов замокано, часть подменено; - зачастую тестируется не реальная логика, а искусственный контекст приложения, который слабо соотносится с реальным, очень часто видел, как такие тесты вырождались в тестирование каких-то синтетических ситуаций и по сути в тестирование моков. Надо еще держать в уме всю эту магию AutoConfigureJdbc и др (т.е. нужно еще потратить немало когнитивных усилий для того, чтобы понять собственно вводные как этот тест работает, а потом уже только разбираться, а что не работает)
На мой взгляд, более рациональный подход это сочетание: юнит тестов и возможно ограниченных slice-тестов( тестирование репозиториев и контроллеров), возможно в некоторых ситуациях с TestContainers( и то нужно еще 10 раз подумать, если есть компонентные тесты)
Не вижу убедительных аргументов в пользу @SpringBootTest как основного инструмента.
Для компонентного тестирования приложения как «чёрного ящика» на мой взгляд лучше подойти по-другому, у нас например это делается так:
1) Собирается Docker Compose с контейнером с нашим сервисом и контейнерами с сервисами, которые необходим для работы нашего приложения( kafka, wiremock, postgres)
2) Вся эта конфигурация поднимается.
3) Далее есть отдельно код, который собственно взаимодействует с сервисом через kafka, rest-api в зависимости от логики для воспроизведения тесткейсов.
При таком подходе код тестовых сценариев по определению отделен от логики приложения( в @SpringBootTest тоже конечно можно так сделать, но тяжело придерживаться такого стиля), логи теста и приложения отдельно, тестирование близко к реальным сценариям.
Конечно и при таком подходе можно реализовать все так плохо, что невозможно будет разобраться, но в целом кажется что легче реализовать понятные тестовые сценарии.
Вы вроде как приглашаете к дискуссии, при этом откровенно и неприкрыто называете потенциальных оппонентов сразу безапелляционно дураками. Т.е. дискуссия даже еще не началась, а вы уже обзываетесь. На какое интересно качество дискуссии Вы рассчитываете и вообще рассчитываете ли?)
А вы знакомы с концепциями TDD, для чего вообще нужны по Вашему юнит тесты?
Какое такое поведение они фиксируют, зачем его фиксировать? Вот Вы написали или сгенерировали какой-то класс с помощью chatgpt, затем сгенерировали тесты и "зафиксировали" поведение. А Вы уверены, что вы зафиксировали то что надо? Как можно полагаться на зафиксированное поведение сгенеренного по реализации теста? Рефакторинг строго говоря бывает разный и не всегда это добавить еще один if в метод и радоваться, что старые тесты не упали, написав еще один новый тест. Что Вы делаете с этими сгенерированными по прошлой реализации тестами? Вы их не писали, получается их можно выкидывать в мусорку и заново генерить, если рефакторинг или добавление новой фичи выливается в что-то чуть большее чем пару строк кода в конце метода? А если бы вы их писали ( возвращаясь к практикам TDD), то они бы Вам в этом рефакторинге помогли, а вот эти Ваши сгенерированные вряд ли помогут. Есть еще такой момент, что масштаб вызываемой боли при написании юнит теста обычно коррелирует с тем насколько нехорошо собственно написана реализация и декомпозирован код, а генерация тестов не дает шанса подумать об этом ( концепции TDD).
Меня не покидает ощущения творящегося идиотизма, как будто тесты пишутся по не для разработки качественного продукта и дальнейшей его поддержки, а для реализации хотелок эффективных менеджеров. Сгенерим 100 классов и 100 классов тестов для них за 5 секунд и все довольны, а то как там в реальности это работает не важно, скорей всего потом будем еще неделю вручную тестировать, но зато все формальные требования закрыты: 100 классов есть и 100 юнитов есть со 100% покрытием.
Современные AI инструменты бесспорно мощные и нужные инструменты, с помощью которых можно оптимизировать работу, но все надо делать с умом и вот эти конкретные фичи в таком виде как рекламируется в статье их использовать - на мой взгляд не имеет ничего общего с повышением эффективности работы разработчика и улучшением качества продукта, а скорее пыль в глаза. Я бы даже сказал, что такая реклама такого использования copilot и др скорее даже вредна для неопытных разработчиков.
Мне одному непонятна ценность таких сгенерированных тестов? Т.е. разработчик пишет реализацию. Затем отдает AI инструменту, AI инструмент успешно генерирует тесты которые проходят без участия разработчика. И какой выхлоп от таких тестов? Даже если опустить за скобки, что в целом хорошо бы работать если не по TDD, то по крайней мере писать тесты параллельно с кодом. Тесты разработанные AI без участия программиста не несут никакой смысловой нагрузки. Сегодня я написал реализацию и сгенерил тесты, завтра мне нужно подправить реализацию - я иду и тоже сгенерю тесты, получается от этих тестов мир лучше не стал. А вот если я как разработчик вложил в реализацию и в тесты этой реализации хоть какую-то идею, дальше при изменении реализации есть шанс, что я отловлю что-то, что я забыл или не продумал, даже если это и приведет к правке тестов - все равно заставит меня критически оценить логику тестов которые были и правда ли я меняю реализацию так, что эти сценарии тоже меняются.
Я не полностью против использования таких инструментов, более того я сам использую для генерации каркаса таких тестов, но вот генерация уже самих тестов и сценариев бездумно - это на мой взгляд какой-то мартышкин труд и имитация деятельности, правда это справедливо и для бездумного написания тестов без AI инструментов, которые проходят.
я этот момент как раз таки и объяснял в статье. в топике хранятся апдейты из БД, вычитав топик любой сервис может восстановить на своей стороне реплику БД, также подчеркнуто, что должен быть правильно выбран ключ.
исходя из вышеизложенного нас как раз таки и интересуют только последние данные по ключу, старые данные нас не интересуют. Для того, чтобы топик с одной стороны не разрастался бесконечно, так одной записи в БД может соотв несколько записей в топике, если мы будем ее обновлять, с другой - чтобы все данные по актуальным ключам не почистились как раз таки и выбрана cleanup.policy = compacted.
вас и предыдущего комментатора я не понял, думал вопрос по поводу что все записи удаляются по времени, это было бы проблемой для представленного подхода. То что старые данные ПО КЛЮЧУ удаляются, изза этого осознанно выбран режим compacted. Никаких проблем при этом не будет, реплика будет актуальна независимо от того, будет ли в топике по ключу несколько значений или только последнее.
данные по ключу в режиме compacted могут быть полностью удалены из топика, если послать recrod с value = tombstone.
Вы обладаете знаниями которых у меня нет. Выше все объяснено и приведены ссылки на оф сайт, там можно изучить как работает режим cleanup.policy=compacted. Да я видел, что был какой-то KIP, где можно задачать cleanup.policy=compacted,delete, но вряд ли вы про это
меня устраивает то, что вы описали, у меня нет цели, чтобы мгновенно получить точечные изменения, в целом сама доставка от момента добавления записи в постгрес, добавления в топик и вычитки занимает тоже довольно неопределенное время и мы к этому готовы, выполняем конфигурацию заранее. Зато я не думаю о том, как устроена внутрянка HM и какие эффекты могут быть в многопоточном приложении.
я повторюсь, я не хочу думать и фантазировать об эффектах и рисковать, мне просто не нужна такая оптимизация, использовать HM или CHM для меня скорей всего при тестировании будет даже статистически неразличимым. Главная оптимизация уже сделана, за данными не ходим в редис, а обращаемся к In-Memory структуре.
Спасибо, что поделились предложением, но меня не убедили. Ваше право так подходить к оптимизации ваших приложение, мне это кажется излишним, даже если это действительно правда.
Я если честно вас не понимаю, особенно про 'ложную надежду' в chm. Данные именно что обновляются в рантайме и перестройка структуры может произойти в любой момент. Моё понимание java memory model и ваши обьяснения на данный момент не позволяют с вами согласиться. Повторюсь chm или hm в рамках моих сервисом будут не различимы, но предпочитаю использовать при малейшем сомнении thread safe структуру. Не важно могут ли быть спецэффекты которые я могу предположить или такие, которые я не могу. Я вот честно не хочу даже об этом думать, есть thread safe структура из стандартной библиотеки и если с ней будут спецэффекты, то я по крайней мере буду уверен, что это не из-за того, что я использовал не thread safe структуру.
"По умолчанию Кафка - это журнал сообщений, и я знаю довольно много людей, которые использовать её даже в роли key value storage считают извращением. " - кажется что ваши знакомые не пользуются всеми возможностями кафки, т.к. confluent в целом пропагандирует другое и много докладов в том числе про это, даже есть такая ksqlDB - где DB прям в названии, хотя конечно это не совсем про DB :)
Я с вами полностью согласен, что представленное мной решение очень специфической для кейса с множеством предусловий и оговорок. В целом такая идея возникла после изучения примеров на сайте confluent по построению kafka stream application и прочтения Kafka Streams In Action. Даже думал начать использовать kafka streams. Но во первых понял, что у нас уже 10ки сервисов написаны с использованием реактивного клиента, которые просто так не перепишешь и кажется, что следуя рекомендациям этих гайдов эти сервисы надо немного по другому организовывать и разбивать на сервисы. Также мне на самом деле не очень понравилось то, как написан код как раз таки с этими Store, из-за иерархии наследования, которая была там, очень сложно кастомизировать так, как мне хотелось. Поэтому пока отложил этот и решил для начала взять оттуда принцип для одного специфичного кейса со справочниками. Т.е. сам kafka streams framework на первый взгляд субъективно не понравился, но с точки зрения описания принципов построения архитектуры приложения на базе Kafka - очень вдохновило, так что сейчас от этого активно использую kafka connect и вот представленное решение.
Я уже упомянул в другом комментарии, что изначально на этапе mvp эти справочники вообще велись в Redis. Но Redis это:
1) это дополнительная точка отказа ( сам кластер надо содержать и поддержать, у нас кстати на проде пару раз была потеря кластера)
2) это дополнительное межсетевое взаимодействие, на которое тратится время при обработке транзакции
3) неудобство ведения ( можно конечно пофиксить это с помощью такой же репликации из postgres через кафку)
А что касается справочников, хотя все таки это наверно не совсем справочники, где что-то динамично меняется, в зависимости от обработанных транзакций например - на этот счет можно также найти примеры в вышеупомянутых ресурсах на confluent, конкретно представленное решение в таком виде не подходит, согласен.
у меня сомнение на этот счет. hash map не tread safe. любое добавление элемента в hash map может привести к перестройке внутренней структуры, таким образом не уверен, что не будет спецэффектов при чтении из других потоков. возможно вы что-то лучше меня знаете или понимаете и в кейсе когда только один поток пишет, а другие читают должно работать или я пропустил какие-то изменения в новых версиях jdk. В любом случае даже если так, я обычно следую принципу: если есть сомнения - используй thread safe классы, такие как Atomic*, ConcurrentHashMap и др. По-моему на каком-то очень крутом докладе на joker, где разбиралось эффекты влечет объявление volatile и какие можно не помечать volatile, в конце был дан совет - при сомнениях лучше использовать оптимизированные под это спец классы, такие как Atomic* ConcurrentHashMap и др, потому что даже зная все детали того, как компилируется и оптимизируется код в jvm, можно не туда свернуть в рассуждения и прийти к неправильному выводу. Так что если мне нужна HashMap в многопоточное приложение, то я даже если честно долго не думаю и беру CHM. Кажется, что даже если вы правы и я что-то упускаю и недопонимаю, вряд ли выигрыш от использования HashMap будет вообще различим в такого рода сервисах, а вот риск получить спецэффекты при работе с HashMap, когда по логам значение будет записано, а в итоге не будет вычитано, точно не хочется потом разбираться с этим.
Нет не сравнивал, т.к. не особо интересно. Убежден, что такую выгрузку можно сделать быстрее, но для меня это не так важно. Мне нужно было убедиться, что на кратно большем объеме инициализация справочника выполняется за удовлетворительное время, т.е. время не сильно отличимое на фоне запуска spring boot сервиса который работает с кафкой ( в самом лучше случае это тоже занимает секунду, но иногда и больше, т.к. требуется время на то, пока консьмерам назначаться партиции, пока произойдет ребалансировка - это может занять и 10ки секунд).
Если я вас правильно понял, вы хотели предложить подход, при котором загружать данные в кэш из БД напрямую а потом периодический его обновлять. Если так, то такое решение не подходи, т.к. если сервис начнет перезапускаться в момент, когда БД не подключена - он не сможет подняться.
При желании при таком подходе можете реализовать такую структуру, которая будет индексировать по разному, насколько хватит вашей фантазии, но это да нетривиально. По умолчанию да согласен, Kafka это key value store и в kafka streams как раз таки на это упор, все реализации хранилищ строятся вокруг того, что это key-value store и даже в Rocks DB Java Api я не нашел ничего, кроме как put(key) get(key) remove(key), хотя в c++ api там есть и про диапазон тоже. Не каждое решение универсально, с этим спорить не буду. У нас кстати есть пару справочников, которые предоставляют api поиска по ключу и активным датам и по диапазону ключей и по префиксу, конечно же там не такая простая ConcurrentHashMap.
По требования возможно дал не так много вводных. Мне они кажутся вполне себе логичными. У нас есть Postgres предоставляемый как сервис. Сказано, что оооооочень редко может быть такое, что ночью нужно сделать какие-то работы, которые приведут к тому, что сервис будет лежать 2-3 часа. По нашему SLA мы не можем обрабатывать поступающую транзакцию более 10 мин ( т.е. нам несмотря на лежащую БД нужно обрабатывать транзакции).
"Редко изменяемые" довольно неточный термин, не знаю "редко у нас изменяемые справочники или нет" в вашей классификации, но как это выглядит: условно бизнес заключает договор с несколькими компаниями и несколькими банками, далее он передает службе поддержки, что к такому то числу нужно уметь обрабатывать поступающие транзакции, служба поддержки готовит справочные данные к данной дате, обычно проводится какое-то боевое тестирование в проде на столе. Таких запросов от бизнеса может быть несколько в неделю, что выливается в изменение/добавление/удаление сотен записей в нескольких справочниках. Может быть и больше в зависимости от нагрузки от бизнеса
Данный подход вполне себе покрывает все данные требования, изменения в справочниках быстро доставляются до сервисов с помощью представленного механизма. В тех исключительных \ситуациях, когда постгрес лежит ночью справочники понятное дело никто и не думает заполнять, все эти работы проводятся днем, в ночное время работают только дежурные.
"данные в кафке хранятся в памяти" - не понял утверждение. Возможно вы имели ввиду данные Из Кафки хранятся в оперативной памяти сервиса - тогда ДА.
Что будет если кластер Кафки перезапуститься?
Если Кластер Кафка не работает - то вся наша система ничего не может делать - это сказано в предусловиях, так что если понадобиться перезапуск Кластера Кафки, то мы скорей всего просто остановим все сервисы, разберемся с кластером, перезапустим его и перезапустим сервисы. Если перезапуск произойдет внештатно, то мы не сможем обрабатывать транзакции дальше до восстановления. Смогут ли сервисы восстановить работу после перезапуска кластера без перезапуска самих сервисов - надо тестировать каждый конкретный сервис и то, как там реализована работа с консюмерами.
Но повторюсь, Кафка у нас считается высокодоступной, поэтому при таком редком маловероятном кейсе легче все выключить и включить, чем тестировать и поддерживать работу при таком кейсе. Поэтому вдруг для вас это важно, переподключение консюмера при перезапуске KafkaGlobalStore, то возможно его нужно немного доработать. Для нас такое усложнение логики не актуально.
Были справочники. Изначально были в редис кластере. В какой-то момент стало неудобно их там вести. Поняли что в этом случае кроме поддержки высокодоступной кафки, нужно еще и поддерживать высокодоступный кластер редис. После нескольких падений кластера редис всем надоел.
Коллега, я ни в коим случае никуда не посылаю, ваше право считать мои ответы неправильными. Я искренне хотел понять и ответить на вопросы предыдущего комментатора. Мне показалось, что я вполне себе ответил, дав ссылку на официальную доку, где можно почитать про разные clenup policy и в скользь упомянул про режиме compaction в статье, возможно не очень удачно, но я и не являюсь профессионалом в этом деле, я всего лишь любитель.
На мой взгляд при желании перейдя по ссылке можно ознакомиться со всеми нюансами, т.к. у человека видно, что нет пока необходимых знаний по этому вопросу, а вот то, что вы называете простым ответом повлекло ,бы за собой еще 10 простых вопросов, исхожу из того, что и в комментарии и в статье это объяснено так, что вы не поняли. Зачем я буду отвечать про ttl, если как вы правильно заметили такая терминология не используется в доках конфигурации - потом кто-то предъявит за ttl, что ввожу в заблуждение. Если есть действительно желание вникнуть в этот момент, то лучше идти к первоисточнику. Моей целью не было осветить все нюансы работы с Кафкой.
Как же не ответил, если написал про retention policy compacted и привёл ссылку и в самой статье и в комментарии, мне показалось что ответил, возможно не подробно, но по ссылке все нюансы описаны
Все тестирование проходит и в ci/cd и локально. Т.е. в ci/cd у нас есть отдельно запуск юнит тестов и отдельно запуск компонентных тестов.
Как выглядят компонентные тесты:
Это отдельный подпроект внутри проекта с сервисом. Там находятся собственно код тестовых сценариев, compose-файлы с помощью которых поднимается окружение необходимо для работы тестируемого сервиса( kafka, postgres и т.д.) и собственно сам сервис.
Режим работы с тестами локально. Поднимаются с помощью gradle таски все контейнеры, необходимы для работы сервиса. Сам сервис можно просто запустить из IDEA или собрать контейнер и также запустить в контейнере с помощью конфигурации в compose файле. Далее запускаются сам код тестов. Если смысл работы сервиса условно заключается в том, что надо вычитать сообщение из топика кафки, обогатить его, послав запрос в другой сервис по ресту и далее положить его в кафку, то тест будет собственно выполнять это: 1) сформируем сообщение в кафку 2) настроим wiremock для эмуляции ответа от внешнего сервиса 3) отправим сообщение в кафку 4) проверим, что в результирующем топике появилось соответствующее сообщение
Режим работы тестов в ci/cd. Все тоже самое с той лишь разницей, что сначала собирается артефакт docker container с сервисом, далее с помощью gradle таски поднимается окружение( kafka, postgres и т.д.),далее поднимается сам контейнер и потом осуществляется запуск компонентных тестов с помощью gradle test из проекта с автотестами.
В целом автотесты вполне себе легко запускаются и локально и в ci cd, у нас нет ничего специализированного для ci cd, скорей наоборот на виртуалках ci cd обычно ресурсов меньше, чем локально.
E2E тесты у нас немного вынесены за процесс сборки артефактов сервисов и прогоняются также в том числе локально и в ci cd уже непосредственно перед релизом.
Cкептически отношусь к использованию @SpringBootTest. В прошлом встречал применения коллегами таких тестов совместно с TestContainers, и у них есть ряд существенных недостатков, кроме собственно того, что описано в статье:
1. Тесты получаются излишне запутанными по структуре.
2. Запуск занимает много времени, что замедляет цикл разработки. Хочется иметь возможность тесты гонять не только в CI/CD.
3. Трудная диагностика сбоев. При падении теста разобраться в причине как правило крайне сложно, зависит конечно от автора, но у меня лично глаз начинает дергаться, если вижу, что упал «SpringBootTest», не видел «понятных» по структуре:
- в логах перемешаны сообщения от TestContainers, тестового фреймворка и бизнес‑логики;
часть компонентов замокано, часть подменено;
- зачастую тестируется не реальная логика, а искусственный контекст приложения, который слабо соотносится с реальным, очень часто видел, как такие тесты вырождались в тестирование каких-то синтетических ситуаций и по сути в тестирование моков. Надо еще держать в уме всю эту магию
AutoConfigureJdbc и др (т.е. нужно еще потратить немало когнитивных усилий для того, чтобы понять собственно вводные как этот тест работает, а потом уже только разбираться, а что не работает)На мой взгляд, более рациональный подход это сочетание: юнит тестов и возможно ограниченных slice-тестов( тестирование репозиториев и контроллеров), возможно в некоторых ситуациях с TestContainers( и то нужно еще 10 раз подумать, если есть компонентные тесты)
Не вижу убедительных аргументов в пользу @SpringBootTest как основного инструмента.
Для компонентного тестирования приложения как «чёрного ящика» на мой взгляд лучше подойти по-другому, у нас например это делается так:
1) Собирается Docker Compose с контейнером с нашим сервисом и контейнерами с сервисами, которые необходим для работы нашего приложения( kafka, wiremock, postgres)
2) Вся эта конфигурация поднимается.
3) Далее есть отдельно код, который собственно взаимодействует с сервисом через kafka, rest-api в зависимости от логики для воспроизведения тесткейсов.
При таком подходе код тестовых сценариев по определению отделен от логики приложения( в @SpringBootTest тоже конечно можно так сделать, но тяжело придерживаться такого стиля), логи теста и приложения отдельно, тестирование близко к реальным сценариям.
Конечно и при таком подходе можно реализовать все так плохо, что невозможно будет разобраться, но в целом кажется что легче реализовать понятные тестовые сценарии.
Вы вроде как приглашаете к дискуссии, при этом откровенно и неприкрыто называете потенциальных оппонентов сразу безапелляционно дураками. Т.е. дискуссия даже еще не началась, а вы уже обзываетесь. На какое интересно качество дискуссии Вы рассчитываете и вообще рассчитываете ли?)
А вы знакомы с концепциями TDD, для чего вообще нужны по Вашему юнит тесты?
Какое такое поведение они фиксируют, зачем его фиксировать? Вот Вы написали или сгенерировали какой-то класс с помощью chatgpt, затем сгенерировали тесты и "зафиксировали" поведение. А Вы уверены, что вы зафиксировали то что надо? Как можно полагаться на зафиксированное поведение сгенеренного по реализации теста? Рефакторинг строго говоря бывает разный и не всегда это добавить еще один if в метод и радоваться, что старые тесты не упали, написав еще один новый тест. Что Вы делаете с этими сгенерированными по прошлой реализации тестами? Вы их не писали, получается их можно выкидывать в мусорку и заново генерить, если рефакторинг или добавление новой фичи выливается в что-то чуть большее чем пару строк кода в конце метода? А если бы вы их писали ( возвращаясь к практикам TDD), то они бы Вам в этом рефакторинге помогли, а вот эти Ваши сгенерированные вряд ли помогут. Есть еще такой момент, что масштаб вызываемой боли при написании юнит теста обычно коррелирует с тем насколько нехорошо собственно написана реализация и декомпозирован код, а генерация тестов не дает шанса подумать об этом ( концепции TDD).
Меня не покидает ощущения творящегося идиотизма, как будто тесты пишутся по не для разработки качественного продукта и дальнейшей его поддержки, а для реализации хотелок эффективных менеджеров. Сгенерим 100 классов и 100 классов тестов для них за 5 секунд и все довольны, а то как там в реальности это работает не важно, скорей всего потом будем еще неделю вручную тестировать, но зато все формальные требования закрыты: 100 классов есть и 100 юнитов есть со 100% покрытием.
Современные AI инструменты бесспорно мощные и нужные инструменты, с помощью которых можно оптимизировать работу, но все надо делать с умом и вот эти конкретные фичи в таком виде как рекламируется в статье их использовать - на мой взгляд не имеет ничего общего с повышением эффективности работы разработчика и улучшением качества продукта, а скорее пыль в глаза. Я бы даже сказал, что такая реклама такого использования copilot и др скорее даже вредна для неопытных разработчиков.
Мне одному непонятна ценность таких сгенерированных тестов? Т.е. разработчик пишет реализацию. Затем отдает AI инструменту, AI инструмент успешно генерирует тесты которые проходят без участия разработчика. И какой выхлоп от таких тестов? Даже если опустить за скобки, что в целом хорошо бы работать если не по TDD, то по крайней мере писать тесты параллельно с кодом. Тесты разработанные AI без участия программиста не несут никакой смысловой нагрузки. Сегодня я написал реализацию и сгенерил тесты, завтра мне нужно подправить реализацию - я иду и тоже сгенерю тесты, получается от этих тестов мир лучше не стал. А вот если я как разработчик вложил в реализацию и в тесты этой реализации хоть какую-то идею, дальше при изменении реализации есть шанс, что я отловлю что-то, что я забыл или не продумал, даже если это и приведет к правке тестов - все равно заставит меня критически оценить логику тестов которые были и правда ли я меняю реализацию так, что эти сценарии тоже меняются.
Я не полностью против использования таких инструментов, более того я сам использую для генерации каркаса таких тестов, но вот генерация уже самих тестов и сценариев бездумно - это на мой взгляд какой-то мартышкин труд и имитация деятельности, правда это справедливо и для бездумного написания тестов без AI инструментов, которые проходят.
я этот момент как раз таки и объяснял в статье. в топике хранятся апдейты из БД, вычитав топик любой сервис может восстановить на своей стороне реплику БД, также подчеркнуто, что должен быть правильно выбран ключ.
исходя из вышеизложенного нас как раз таки и интересуют только последние данные по ключу, старые данные нас не интересуют. Для того, чтобы топик с одной стороны не разрастался бесконечно, так одной записи в БД может соотв несколько записей в топике, если мы будем ее обновлять, с другой - чтобы все данные по актуальным ключам не почистились как раз таки и выбрана cleanup.policy = compacted.
вас и предыдущего комментатора я не понял, думал вопрос по поводу что все записи удаляются по времени, это было бы проблемой для представленного подхода. То что старые данные ПО КЛЮЧУ удаляются, изза этого осознанно выбран режим compacted. Никаких проблем при этом не будет, реплика будет актуальна независимо от того, будет ли в топике по ключу несколько значений или только последнее.
данные по ключу в режиме compacted могут быть полностью удалены из топика, если послать recrod с value = tombstone.
Вы обладаете знаниями которых у меня нет. Выше все объяснено и приведены ссылки на оф сайт, там можно изучить как работает режим cleanup.policy=compacted. Да я видел, что был какой-то KIP, где можно задачать cleanup.policy=compacted,delete, но вряд ли вы про это
меня устраивает то, что вы описали, у меня нет цели, чтобы мгновенно получить точечные изменения, в целом сама доставка от момента добавления записи в постгрес, добавления в топик и вычитки занимает тоже довольно неопределенное время и мы к этому готовы, выполняем конфигурацию заранее. Зато я не думаю о том, как устроена внутрянка HM и какие эффекты могут быть в многопоточном приложении.
я повторюсь, я не хочу думать и фантазировать об эффектах и рисковать, мне просто не нужна такая оптимизация, использовать HM или CHM для меня скорей всего при тестировании будет даже статистически неразличимым. Главная оптимизация уже сделана, за данными не ходим в редис, а обращаемся к In-Memory структуре.
Спасибо, что поделились предложением, но меня не убедили. Ваше право так подходить к оптимизации ваших приложение, мне это кажется излишним, даже если это действительно правда.
Я если честно вас не понимаю, особенно про 'ложную надежду' в chm. Данные именно что обновляются в рантайме и перестройка структуры может произойти в любой момент. Моё понимание java memory model и ваши обьяснения на данный момент не позволяют с вами согласиться. Повторюсь chm или hm в рамках моих сервисом будут не различимы, но предпочитаю использовать при малейшем сомнении thread safe структуру. Не важно могут ли быть спецэффекты которые я могу предположить или такие, которые я не могу. Я вот честно не хочу даже об этом думать, есть thread safe структура из стандартной библиотеки и если с ней будут спецэффекты, то я по крайней мере буду уверен, что это не из-за того, что я использовал не thread safe структуру.
спасибо за оценку!
"По умолчанию Кафка - это журнал сообщений, и я знаю довольно много людей, которые использовать её даже в роли key value storage считают извращением. " - кажется что ваши знакомые не пользуются всеми возможностями кафки, т.к. confluent в целом пропагандирует другое и много докладов в том числе про это, даже есть такая ksqlDB - где DB прям в названии, хотя конечно это не совсем про DB :)
Я с вами полностью согласен, что представленное мной решение очень специфической для кейса с множеством предусловий и оговорок. В целом такая идея возникла после изучения примеров на сайте confluent по построению kafka stream application и прочтения Kafka Streams In Action. Даже думал начать использовать kafka streams. Но во первых понял, что у нас уже 10ки сервисов написаны с использованием реактивного клиента, которые просто так не перепишешь и кажется, что следуя рекомендациям этих гайдов эти сервисы надо немного по другому организовывать и разбивать на сервисы. Также мне на самом деле не очень понравилось то, как написан код как раз таки с этими Store, из-за иерархии наследования, которая была там, очень сложно кастомизировать так, как мне хотелось. Поэтому пока отложил этот и решил для начала взять оттуда принцип для одного специфичного кейса со справочниками. Т.е. сам kafka streams framework на первый взгляд субъективно не понравился, но с точки зрения описания принципов построения архитектуры приложения на базе Kafka - очень вдохновило, так что сейчас от этого активно использую kafka connect и вот представленное решение.
Я уже упомянул в другом комментарии, что изначально на этапе mvp эти справочники вообще велись в Redis. Но Redis это:
1) это дополнительная точка отказа ( сам кластер надо содержать и поддержать, у нас кстати на проде пару раз была потеря кластера)
2) это дополнительное межсетевое взаимодействие, на которое тратится время при обработке транзакции
3) неудобство ведения ( можно конечно пофиксить это с помощью такой же репликации из postgres через кафку)
А что касается справочников, хотя все таки это наверно не совсем справочники, где что-то динамично меняется, в зависимости от обработанных транзакций например - на этот счет можно также найти примеры в вышеупомянутых ресурсах на confluent, конкретно представленное решение в таком виде не подходит, согласен.
у меня сомнение на этот счет. hash map не tread safe. любое добавление элемента в hash map может привести к перестройке внутренней структуры, таким образом не уверен, что не будет спецэффектов при чтении из других потоков. возможно вы что-то лучше меня знаете или понимаете и в кейсе когда только один поток пишет, а другие читают должно работать или я пропустил какие-то изменения в новых версиях jdk.
В любом случае даже если так, я обычно следую принципу: если есть сомнения - используй thread safe классы, такие как Atomic*, ConcurrentHashMap и др. По-моему на каком-то очень крутом докладе на joker, где разбиралось эффекты влечет объявление volatile и какие можно не помечать volatile, в конце был дан совет - при сомнениях лучше использовать оптимизированные под это спец классы, такие как Atomic* ConcurrentHashMap и др, потому что даже зная все детали того, как компилируется и оптимизируется код в jvm, можно не туда свернуть в рассуждения и прийти к неправильному выводу. Так что если мне нужна HashMap в многопоточное приложение, то я даже если честно долго не думаю и беру CHM.
Кажется, что даже если вы правы и я что-то упускаю и недопонимаю, вряд ли выигрыш от использования HashMap будет вообще различим в такого рода сервисах, а вот риск получить спецэффекты при работе с HashMap, когда по логам значение будет записано, а в итоге не будет вычитано, точно не хочется потом разбираться с этим.
Нет не сравнивал, т.к. не особо интересно. Убежден, что такую выгрузку можно сделать быстрее, но для меня это не так важно. Мне нужно было убедиться, что на кратно большем объеме инициализация справочника выполняется за удовлетворительное время, т.е. время не сильно отличимое на фоне запуска spring boot сервиса который работает с кафкой ( в самом лучше случае это тоже занимает секунду, но иногда и больше, т.к. требуется время на то, пока консьмерам назначаться партиции, пока произойдет ребалансировка - это может занять и 10ки секунд).
Если я вас правильно понял, вы хотели предложить подход, при котором загружать данные в кэш из БД напрямую а потом периодический его обновлять. Если так, то такое решение не подходи, т.к. если сервис начнет перезапускаться в момент, когда БД не подключена - он не сможет подняться.
При желании при таком подходе можете реализовать такую структуру, которая будет индексировать по разному, насколько хватит вашей фантазии, но это да нетривиально. По умолчанию да согласен, Kafka это key value store и в kafka streams как раз таки на это упор, все реализации хранилищ строятся вокруг того, что это key-value store и даже в Rocks DB Java Api я не нашел ничего, кроме как put(key) get(key) remove(key), хотя в c++ api там есть и про диапазон тоже. Не каждое решение универсально, с этим спорить не буду. У нас кстати есть пару справочников, которые предоставляют api поиска по ключу и активным датам и по диапазону ключей и по префиксу, конечно же там не такая простая ConcurrentHashMap.
По требования возможно дал не так много вводных. Мне они кажутся вполне себе логичными. У нас есть Postgres предоставляемый как сервис. Сказано, что оооооочень редко может быть такое, что ночью нужно сделать какие-то работы, которые приведут к тому, что сервис будет лежать 2-3 часа. По нашему SLA мы не можем обрабатывать поступающую транзакцию более 10 мин ( т.е. нам несмотря на лежащую БД нужно обрабатывать транзакции).
"Редко изменяемые" довольно неточный термин, не знаю "редко у нас изменяемые справочники или нет" в вашей классификации, но как это выглядит: условно бизнес заключает договор с несколькими компаниями и несколькими банками, далее он передает службе поддержки, что к такому то числу нужно уметь обрабатывать поступающие транзакции, служба поддержки готовит справочные данные к данной дате, обычно проводится какое-то боевое тестирование в проде на столе. Таких запросов от бизнеса может быть несколько в неделю, что выливается в изменение/добавление/удаление сотен записей в нескольких справочниках. Может быть и больше в зависимости от нагрузки от бизнеса
Данный подход вполне себе покрывает все данные требования, изменения в справочниках быстро доставляются до сервисов с помощью представленного механизма. В тех исключительных \ситуациях, когда постгрес лежит ночью справочники понятное дело никто и не думает заполнять, все эти работы проводятся днем, в ночное время работают только дежурные.
вкратце - на диске, но это наверное совсем другая история за пределами данного топика
буквально вручную на этапе mvp службой поддержки
да все правильно вы поняли, только не всех топиков, а тех топиков-справочников, которые используются в конкретном сервисе
"данные в кафке хранятся в памяти" - не понял утверждение. Возможно вы имели ввиду данные Из Кафки хранятся в оперативной памяти сервиса - тогда ДА.
Что будет если кластер Кафки перезапуститься?
Если Кластер Кафка не работает - то вся наша система ничего не может делать - это сказано в предусловиях, так что если понадобиться перезапуск Кластера Кафки, то мы скорей всего просто остановим все сервисы, разберемся с кластером, перезапустим его и перезапустим сервисы. Если перезапуск произойдет внештатно, то мы не сможем обрабатывать транзакции дальше до восстановления. Смогут ли сервисы восстановить работу после перезапуска кластера без перезапуска самих сервисов - надо тестировать каждый конкретный сервис и то, как там реализована работа с консюмерами.
Но повторюсь, Кафка у нас считается высокодоступной, поэтому при таком редком маловероятном кейсе легче все выключить и включить, чем тестировать и поддерживать работу при таком кейсе. Поэтому вдруг для вас это важно, переподключение консюмера при перезапуске KafkaGlobalStore, то возможно его нужно немного доработать. Для нас такое усложнение логики не актуально.
Были справочники. Изначально были в редис кластере. В какой-то момент стало неудобно их там вести. Поняли что в этом случае кроме поддержки высокодоступной кафки, нужно еще и поддерживать высокодоступный кластер редис. После нескольких падений кластера редис всем надоел.
Коллега, я ни в коим случае никуда не посылаю, ваше право считать мои ответы неправильными. Я искренне хотел понять и ответить на вопросы предыдущего комментатора. Мне показалось, что я вполне себе ответил, дав ссылку на официальную доку, где можно почитать про разные clenup policy и в скользь упомянул про режиме compaction в статье, возможно не очень удачно, но я и не являюсь профессионалом в этом деле, я всего лишь любитель.
На мой взгляд при желании перейдя по ссылке можно ознакомиться со всеми нюансами, т.к. у человека видно, что нет пока необходимых знаний по этому вопросу, а вот то, что вы называете простым ответом повлекло ,бы за собой еще 10 простых вопросов, исхожу из того, что и в комментарии и в статье это объяснено так, что вы не поняли. Зачем я буду отвечать про ttl, если как вы правильно заметили такая терминология не используется в доках конфигурации - потом кто-то предъявит за ttl, что ввожу в заблуждение. Если есть действительно желание вникнуть в этот момент, то лучше идти к первоисточнику. Моей целью не было осветить все нюансы работы с Кафкой.
Как же не ответил, если написал про retention policy compacted и привёл ссылку и в самой статье и в комментарии, мне показалось что ответил, возможно не подробно, но по ссылке все нюансы описаны