HikariCP и Postgresql
Чтобы выполнить запрос в базе данных, сервер приложений должен установить с ней соединение. Это может быть затратно, т.к. надо открыть соединение через драйвер, открыть TCP-сокет, выполнить запрос, закрыть сокет, закрыть соединение. Таких подключений может быть сотни и тысячи, из-за чего лимит оперативной памяти может быстро исчерпаться.
Поэтому при обращении к БД клиенты (сервера приложений) используют пулы соединений. В случае, если в пуле есть свободное соединение, оно берется из пула. Если пул исчерпан, то запросы встают в очередь и ждут свободного соединения.
У нас на проекте в качестве сonnection pooler используется HikariCP. С базой данных Oracle приложение работало без проблем, но после перехода на Postgresql БД начала падать по непонятной причине. Оказалось, что hikari заполняет пул сразу максимальным количеством соединений, а не создает их постепенно. Поэтому все сервисы резервировали максимум соединений, и БД не выдерживала. Postgresql каждое соединение обрабатывает как отдельный процесс, а Oracle - как поток внутри общего процесса. В результате Oracle лучше держит большее число соединений.
Проблема решилась установлением корректных настроек HikariCP.
#рабочее
Чтобы выполнить запрос в базе данных, сервер приложений должен установить с ней соединение. Это может быть затратно, т.к. надо открыть соединение через драйвер, открыть TCP-сокет, выполнить запрос, закрыть сокет, закрыть соединение. Таких подключений может быть сотни и тысячи, из-за чего лимит оперативной памяти может быстро исчерпаться.
Поэтому при обращении к БД клиенты (сервера приложений) используют пулы соединений. В случае, если в пуле есть свободное соединение, оно берется из пула. Если пул исчерпан, то запросы встают в очередь и ждут свободного соединения.
У нас на проекте в качестве сonnection pooler используется HikariCP. С базой данных Oracle приложение работало без проблем, но после перехода на Postgresql БД начала падать по непонятной причине. Оказалось, что hikari заполняет пул сразу максимальным количеством соединений, а не создает их постепенно. Поэтому все сервисы резервировали максимум соединений, и БД не выдерживала. Postgresql каждое соединение обрабатывает как отдельный процесс, а Oracle - как поток внутри общего процесса. В результате Oracle лучше держит большее число соединений.
Проблема решилась установлением корректных настроек HikariCP.
#рабочее
👍8
В системе, которую разрабатывает наша команда, у пользователей есть функция добавления описания различным продуктам. Появилась необходимость проанализировать, насколько осмысленно заполняются эти описания. Для этого нужно было проверить соответствующую колонку из БД Postgresql на то, являются ли ее значения комбинацией определенных строк, не несущих смысла, таких как 'описание', 'данные', 'продукт', всяких специальных символов и т.п.
Решение в лоб - просто сделать цепочку replace, заменяя каждое встреченное слово-заменитель на пустую строку, и проверить, превратилась ли все описание в пустую строку:
Но учитывая, что таких слов для проверки было более 100, это было бы весьма тяжело читать и поддерживать. Да и сам запрос немаленький, в нем рассчитываются и другие параметры.
Намного удобнее сделать проверку с использованием регулярного выражения:
Regexp_replace заменяет любое из слов, перечисленных в круглых скобках через черту, на пустую строку. Параметр g указывает на то, что заменяем все совпадения, а нет только первое встреченное, i - что игнорируем верхний/нижний регистр.
Еще удобнее не перечислять слова в теле запроса, а поместить их в отдельную таблицу. Так в будущем будет легко добавлять новые.
Тогда запрос будет таким:
Здесь с помощью string_agg собираем строку-паттерн для регулярного выражения и затем используем ее в функции regexp_replace.
В какой-то момент во время экспериментов показалось, что регулярные выражения не работают для этой задачи, т.к. на любое описание проверка возвращала true, как будто все описания состоят из заменителей. Оказалось, что проблема в точке. В regex выражении она означает любой символ, соответственно все описания подходили под шаблон. Стоило записать ее с обратной чертой \. и все заработало как надо.
#рабочее
Решение в лоб - просто сделать цепочку replace, заменяя каждое встреченное слово-заменитель на пустую строку, и проверить, превратилась ли все описание в пустую строку:
select id, denoscription, replace(replace(replace....replace(denoscription, 'описание', '')...'') ...'') = '' has_no_meaning
from product p
Но учитывая, что таких слов для проверки было более 100, это было бы весьма тяжело читать и поддерживать. Да и сам запрос немаленький, в нем рассчитываются и другие параметры.
Намного удобнее сделать проверку с использованием регулярного выражения:
select id, denoscription, regexp_replace(denoscription, '(описание|продукт|...|данные|-|,|\.)' , '', 'gi')) = '' has_no_meaning
from product p
Regexp_replace заменяет любое из слов, перечисленных в круглых скобках через черту, на пустую строку. Параметр g указывает на то, что заменяем все совпадения, а нет только первое встреченное, i - что игнорируем верхний/нижний регистр.
Еще удобнее не перечислять слова в теле запроса, а поместить их в отдельную таблицу. Так в будущем будет легко добавлять новые.
create table t_descr_substitute (
substitute varchar(2000)
);
insert into t_descr_substitute (substitute) values ('описание'); ...
Тогда запрос будет таким:
select id, denoscription, regexp_replace(denoscription, a.pattern, '', 'gi') = '' has_no_meaning
from product p
join ( select '(' + string_agg(substitute, '|') + ')' pattern from t_descr_substitute ) a on 1=1
Здесь с помощью string_agg собираем строку-паттерн для регулярного выражения и затем используем ее в функции regexp_replace.
В какой-то момент во время экспериментов показалось, что регулярные выражения не работают для этой задачи, т.к. на любое описание проверка возвращала true, как будто все описания состоят из заменителей. Оказалось, что проблема в точке. В regex выражении она означает любой символ, соответственно все описания подходили под шаблон. Стоило записать ее с обратной чертой \. и все заработало как надо.
#рабочее
🤔6💅1
Pet-проект
У меня есть pet-проект, на котором тренируюсь в использовании новых технологий. Основной код был написан около полутора лет назад, и недавно решила его пересмотреть. Заметила много моментов, которые можно улучшить, и провела рефакторинг:
▫️ Привела API в соответствие с принципами REST. Было не совсем понятно, для чего нужен тот или иной эндпойнт, из-за не очень логичного разделения методов по контроллерам. Много лишних path variables переделала в request parameters. Итоговый вариант оказался чище и понятней.
▫️ Доработала UI. Добавила datepicker-s и datetimepicker-s там, где раньше был просто текстовый ввод дат. Привела формы и таблицы во всем проекте к единому формату, а также поправила поля в формах, чтобы выглядели одинаково.
▫️ Переделала генерацию ключей для entity. Ранее использовался один генератор для всех сущностей, поэтому при ручном обновлении БД через скрипты приходилось внимательно следить, чтобы не перепутать id сущностей разных типов, иначе возникали ошибки.
▫️ Заметила, что при старте контейнера с базой данных скрипты инициализации отрабатывают довольно медленно. Оказалось, что изначально сгенерированные тестовые данные выгрузила из БД отдельными insert для каждой строки. После объединения строк инициализация пошла гораздо быстрее.
▫️ Провела также рефакторинг классов и методов, которые показались теперь излишне запутанными.
В планах — поднять зависимости до самых новых версий и отрефакторить deprecated код, появившийся за это время.
У меня есть pet-проект, на котором тренируюсь в использовании новых технологий. Основной код был написан около полутора лет назад, и недавно решила его пересмотреть. Заметила много моментов, которые можно улучшить, и провела рефакторинг:
▫️ Привела API в соответствие с принципами REST. Было не совсем понятно, для чего нужен тот или иной эндпойнт, из-за не очень логичного разделения методов по контроллерам. Много лишних path variables переделала в request parameters. Итоговый вариант оказался чище и понятней.
▫️ Доработала UI. Добавила datepicker-s и datetimepicker-s там, где раньше был просто текстовый ввод дат. Привела формы и таблицы во всем проекте к единому формату, а также поправила поля в формах, чтобы выглядели одинаково.
▫️ Переделала генерацию ключей для entity. Ранее использовался один генератор для всех сущностей, поэтому при ручном обновлении БД через скрипты приходилось внимательно следить, чтобы не перепутать id сущностей разных типов, иначе возникали ошибки.
▫️ Заметила, что при старте контейнера с базой данных скрипты инициализации отрабатывают довольно медленно. Оказалось, что изначально сгенерированные тестовые данные выгрузила из БД отдельными insert для каждой строки. После объединения строк инициализация пошла гораздо быстрее.
▫️ Провела также рефакторинг классов и методов, которые показались теперь излишне запутанными.
В планах — поднять зависимости до самых новых версий и отрефакторить deprecated код, появившийся за это время.
👍8
Сейчас на рабочем проекте провожу миграцию сервисов со Spring Boot 2 на Spring Boot 3. Вот некоторые шаги этого процесса:
➖ Для начала подключить зависимость spring-boot-properties-migrator, которая позволяет анализировать файлы конфигурации (application.yml) в рантайме, и автоматически определяет устаревшие или переименованные свойства.
➖ Перейти на Java 17 с 11, т.к Spring Boot 3 не поддерживает более ранние версии.
➖ Перевести Swagger со Springfox на Springdoc. Обе эти библиотеки служат для интеграции Swagger со Spring Boot. Но Springfox уже не развивается (последний коммит в репозитории был в 2021), не совместима со Spring Boot 3 и не поддерживает OpenAPI 3.
➖ Часть диалектов hibernate устарели, т.к. в Hibernate 6 появился механизм автоматического определения диалекта на основе используемой базы данных. Поэтому больше нет нужды использовать диалекты, специфичные для конкретной версии БД, например PostgreSQL10Dialect.
➖ Поправить конфигурации стартеров. В Spring Boot 3 автоконфигурации рекомендуется объявлять в файле META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, а не в spring.factories..
➖ Перейти на Spring Security 6. Требуется обновление кода, т.к. методы для конфигурации авторизации и аутентификации значительно изменились.
#рабочее
➖ Для начала подключить зависимость spring-boot-properties-migrator, которая позволяет анализировать файлы конфигурации (application.yml) в рантайме, и автоматически определяет устаревшие или переименованные свойства.
➖ Перейти на Java 17 с 11, т.к Spring Boot 3 не поддерживает более ранние версии.
➖ Перевести Swagger со Springfox на Springdoc. Обе эти библиотеки служат для интеграции Swagger со Spring Boot. Но Springfox уже не развивается (последний коммит в репозитории был в 2021), не совместима со Spring Boot 3 и не поддерживает OpenAPI 3.
➖ Часть диалектов hibernate устарели, т.к. в Hibernate 6 появился механизм автоматического определения диалекта на основе используемой базы данных. Поэтому больше нет нужды использовать диалекты, специфичные для конкретной версии БД, например PostgreSQL10Dialect.
➖ Поправить конфигурации стартеров. В Spring Boot 3 автоконфигурации рекомендуется объявлять в файле META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, а не в spring.factories..
➖ Перейти на Spring Security 6. Требуется обновление кода, т.к. методы для конфигурации авторизации и аутентификации значительно изменились.
#рабочее
👍4
Во время перевода одного из приложений на Spring Boot 3 возникла ошибка - оказалось, что в приложении добавлены в качестве зависимостей два spring boot стартера, которые содержали конфигурационные бины с одинаковыми именами.
После исправления названия одного из бинов возникла другая ошибка — Spring не мог определить, какой из бинов одинакового типа использовать. В приложении нужен был бин из стартера №1. Возможные решения:
1. Пометить бин из стартера №1 как
2. Пометить бин из стартера №2 аннотацией
3. В моем случае, оказалось возможным провести рефакторинг стартеров, и первый стартер был подключен во второй в качестве зависимости, а бин из второго стартера соответственно был удален.
#рабочее
После исправления названия одного из бинов возникла другая ошибка — Spring не мог определить, какой из бинов одинакового типа использовать. В приложении нужен был бин из стартера №1. Возможные решения:
1. Пометить бин из стартера №1 как
@Primary. Тогда использоваться будет именно он. 2. Пометить бин из стартера №2 аннотацией
@ConditionalOnMissingBean, чтобы он создавался только в случае, не существует первого, т.е. если не подключен первый стартер (для сервисов, которые используют только второй стартер).3. В моем случае, оказалось возможным провести рефакторинг стартеров, и первый стартер был подключен во второй в качестве зависимости, а бин из второго стартера соответственно был удален.
#рабочее
👍3
Тестирование под различными профилями Spring
Подготовила очередной сервис для перехода на Spring Boot 3.
Тесты прошли успешно, локально сервис запускался успешно. Уже собиралась выкладывать правки на ревью.
Но просматривая файлы для коммита, вспомнила, что на тестовом стенде надо отключить Swagger, и решила проверить, что он действительно не доступен при работе под профилем test. И вот запускаю приложение - а оно не стартует. Нет спрингового бина, который требуется в кастомизированном OpenApiWebMvcResource. При чтении документации выяснилось, что нужный бин не создается при отключении свойства springdoc.api-docs.
Для фикса оказалось достаточно добавить к ресурсу аннотацию @ConditionalOnBean(SpringDocConfiguration.class), чтобы он вообще не создавался при отключенном Swagger.
Но из этой ошибки вынесла для себя важность тестирования приложения под всеми объявленными профилями. Ведь если бы не проверила этот момент локально, то баг выявился бы лишь на тестовом стенде, что потребовало бы отдельного фикса, пересборки дистрибутива, повторного развертывания, а также затронуло бы команду тестирования. А сейчас фикс получился быстрым и безболезненным. Поэтому в список необходимых проверок перед созданием MR для себя добавила также тестирование приложения под разными профилями.
Подготовила очередной сервис для перехода на Spring Boot 3.
Тесты прошли успешно, локально сервис запускался успешно. Уже собиралась выкладывать правки на ревью.
Но просматривая файлы для коммита, вспомнила, что на тестовом стенде надо отключить Swagger, и решила проверить, что он действительно не доступен при работе под профилем test. И вот запускаю приложение - а оно не стартует. Нет спрингового бина, который требуется в кастомизированном OpenApiWebMvcResource. При чтении документации выяснилось, что нужный бин не создается при отключении свойства springdoc.api-docs.
Для фикса оказалось достаточно добавить к ресурсу аннотацию @ConditionalOnBean(SpringDocConfiguration.class), чтобы он вообще не создавался при отключенном Swagger.
Но из этой ошибки вынесла для себя важность тестирования приложения под всеми объявленными профилями. Ведь если бы не проверила этот момент локально, то баг выявился бы лишь на тестовом стенде, что потребовало бы отдельного фикса, пересборки дистрибутива, повторного развертывания, а также затронуло бы команду тестирования. А сейчас фикс получился быстрым и безболезненным. Поэтому в список необходимых проверок перед созданием MR для себя добавила также тестирование приложения под разными профилями.
✍1🤔1
Как Spring ищет бины при старте приложения?
Во время разработки мне понадобилось создать бин с функционалом, который нужен в нескольких микросервисах. Для того, чтобы переиспользовать код и не дублировать логику в этих сервисах, решила вынести бин в уже существующую библиотеку. В связи с этим решила написать небольшую заметку о том, как Spring ищет кандидатов для создания бинов.
Если просто создать класс с нужным функционалом в библиотеке, пометить его как
В приложении Spring Boot, application класс обычно помечается аннотацией
Spring не сканирует весь classpath или все приложение для поиска бинов.
Если надо инжектировать в класс бин из подключенной библиотеки, например ru.mylib.MyBean, можно, например, добавить к application классу аннотацию
Есть и другой способ - использовать spring boot starters.
Чем стартер отличается от библиотеки? Помимо того, что стартер обычно содержит набор библиотек, стартеры включают в себя автоконфигурации.
Автоконфигурация - это класс конфигурации, помеченный анннотацией
В частности, можно превратить нашу библиотеку myLib в стартер, добавив автоконфигурацию с аннотацией
Во время разработки мне понадобилось создать бин с функционалом, который нужен в нескольких микросервисах. Для того, чтобы переиспользовать код и не дублировать логику в этих сервисах, решила вынести бин в уже существующую библиотеку. В связи с этим решила написать небольшую заметку о том, как Spring ищет кандидатов для создания бинов.
Если просто создать класс с нужным функционалом в библиотеке, пометить его как
@Component, и подключить библиотеку в проект в качестве зависимости, создаст ли фреймворк этот бин? Ответ - нет. В приложении Spring Boot, application класс обычно помечается аннотацией
@SpringBootApplication, которая включает в себя аннотацию @ComponentScan. И фреймворк будет сканировать на предмет наличия бинов только пакет, в котором объявлен класс помеченный какой-либо из этих аннотаций, и его подпакеты. Spring не сканирует весь classpath или все приложение для поиска бинов.
Если надо инжектировать в класс бин из подключенной библиотеки, например ru.mylib.MyBean, можно, например, добавить к application классу аннотацию
@ComponentScan("ru.mylib").Есть и другой способ - использовать spring boot starters.
Чем стартер отличается от библиотеки? Помимо того, что стартер обычно содержит набор библиотек, стартеры включают в себя автоконфигурации.
Автоконфигурация - это класс конфигурации, помеченный анннотацией
@Configuration или @AutoConfiguration. В стартере они должны быть перечислены в файле spring.factories или org.springframework.boot. autoconfigure.AutoConfiguration.imports. При запуске приложения Spring Boot сканирует classpath, и находит все jar с данными файлами. И создает все бины, которые объявлены в файлах автоконфигурации.В частности, можно превратить нашу библиотеку myLib в стартер, добавив автоконфигурацию с аннотацией
@ComponentScan("ru.mylib"), и тогда все бины из данного пакета будут созданы и доступны во время работы приложения.Продолжаю проводить технические интервью в компанию.
Немного заметок за 3 месяца:
1. В начале интервью стала лучше определять уровень кандидата, и теперь стараюсь задавать вопросы, отталкиваясь от уровня, а не просто пробегая по всем вопросам из списка.
2. Часто делаю акцент на Spring, т.к. вопросы по чистой Java у всех на слуху, и обычно кандидаты отвечают нормально.
3. При этом, иногда на популярных и вроде как простых вопросах по Java обнаруживается, что кандидат просто выучил на поверхностном уровне. А копнешь чуть глубже - уже понимания как такового нет. Поэтому такие вопросы тоже обязательно задаю.
3. Встречаю нередко, что кандидаты не понимают сложностей алгоритмов/используемых структур данных, и просто заучивают. Был случай, когда человек правильно рассказал сложности методов стандартных СД, но неожиданно не смог ответить про сложность вложенного цикла.
4. Очень интересно и ценно, когда кандидат не просто отвечает на вопрос, но приводит пример из своего опыта. Такое обычно встречаю у сильных кандидатов.
5. Стало проще соблюдать тайминг, и возвращать кандидата к теме, если разговор уходит в сторону. Но до сих пор порой выхожу за пределы стандартных полутора часов, т.к. хочется составить максимально объективное мнение. И бывает, что под конец интервью звучат именно те вопросы, которые повышают общую оценку.
Немного заметок за 3 месяца:
1. В начале интервью стала лучше определять уровень кандидата, и теперь стараюсь задавать вопросы, отталкиваясь от уровня, а не просто пробегая по всем вопросам из списка.
2. Часто делаю акцент на Spring, т.к. вопросы по чистой Java у всех на слуху, и обычно кандидаты отвечают нормально.
3. При этом, иногда на популярных и вроде как простых вопросах по Java обнаруживается, что кандидат просто выучил на поверхностном уровне. А копнешь чуть глубже - уже понимания как такового нет. Поэтому такие вопросы тоже обязательно задаю.
3. Встречаю нередко, что кандидаты не понимают сложностей алгоритмов/используемых структур данных, и просто заучивают. Был случай, когда человек правильно рассказал сложности методов стандартных СД, но неожиданно не смог ответить про сложность вложенного цикла.
4. Очень интересно и ценно, когда кандидат не просто отвечает на вопрос, но приводит пример из своего опыта. Такое обычно встречаю у сильных кандидатов.
5. Стало проще соблюдать тайминг, и возвращать кандидата к теме, если разговор уходит в сторону. Но до сих пор порой выхожу за пределы стандартных полутора часов, т.к. хочется составить максимально объективное мнение. И бывает, что под конец интервью звучат именно те вопросы, которые повышают общую оценку.
🔥5👍1
Testcontainers и неудачные тесты
В последнее много занимаюсь интеграционными тестами на тестконтейнерах.
Во время тестового прогона в какой-то момент получила ошибку - контейнер не создается из-за нехватки памяти на сервере. Подключилась к серверу, выполнила docker ps - а там крутится множество контейнеров kafka и postgres, которые я использую в тестах.
Потушила висящие контейнеры, тесты снова стали запускаться, но остался вопрос, что это было? Ведь тестконтейнеры должны автоматически останавливаться и удаляться после прогона теста. Опытным путем выяснила, что контейнеры не удаляются, если тест был остановлен принудительно.
Жизненным циклом тестконтейнеров управляет специальный контейнер ryuk. Он живет, пока живет JVM, и поэтому логично, при принудительной остановке JVM он экстренно завешает свою работу, не успевая остановить другие контейнеры.
Но неожиданно оказалось, что у коллеги, при точно таком же запуске и остановке тестов, контейнеры удаляются ryuk-ом.
Стала разбираться, почему у меня ruyk завершается в первую очередь, и не успевает удалить другие контейнеры, а у коллеги сначала удаляет, а потом уже сам останавливается? Оказалось, что это зависит от версии тестконтейнеров. Я использовала самую новую, 1.20.2, а коллега более раннюю.
Чтобы в будущем другие разработчики не сталкивались с непредсказуемыми ошибками, задокументировала это поведение в спецификации к родительскому тестовому классу.
В последнее много занимаюсь интеграционными тестами на тестконтейнерах.
Во время тестового прогона в какой-то момент получила ошибку - контейнер не создается из-за нехватки памяти на сервере. Подключилась к серверу, выполнила docker ps - а там крутится множество контейнеров kafka и postgres, которые я использую в тестах.
Потушила висящие контейнеры, тесты снова стали запускаться, но остался вопрос, что это было? Ведь тестконтейнеры должны автоматически останавливаться и удаляться после прогона теста. Опытным путем выяснила, что контейнеры не удаляются, если тест был остановлен принудительно.
Жизненным циклом тестконтейнеров управляет специальный контейнер ryuk. Он живет, пока живет JVM, и поэтому логично, при принудительной остановке JVM он экстренно завешает свою работу, не успевая остановить другие контейнеры.
Но неожиданно оказалось, что у коллеги, при точно таком же запуске и остановке тестов, контейнеры удаляются ryuk-ом.
Стала разбираться, почему у меня ruyk завершается в первую очередь, и не успевает удалить другие контейнеры, а у коллеги сначала удаляет, а потом уже сам останавливается? Оказалось, что это зависит от версии тестконтейнеров. Я использовала самую новую, 1.20.2, а коллега более раннюю.
Чтобы в будущем другие разработчики не сталкивались с непредсказуемыми ошибками, задокументировала это поведение в спецификации к родительскому тестовому классу.
👍6
Код и дизайн
Как понять назначение системы?
Раньше думала, что надо внимательно изучить код, тогда вся система станет автоматически ясна.
Но оказалось, это неверно.
Нужно изучить именно дизайн, документацию. Дизайн - это как раз верхнеуровневое описание системы. Одному дизайну может соответствовать множество реализаций. Поэтому из кода и нельзя понять назначение системы. Но в идеале разработчику нужно стремиться к такой реализации, чтобы ее невозможно было сопоставить ни с каким другим дизайном.
Для закрепления этого принципа решила переписать несколько классов из моего пет-проекта.
Для начала выбрала класс, который представляет собой генератор списка случайных координат. Эти координаты потом используются для построения маршрута на UI. Логика довольно простая - есть метод, который принимает начальную и конечную координату поездки, максимальную скорость и время, с каким интервалом генерируются координаты. Для получения списка точек нужно обратиться к внешнему API, преобразовать результат, а затем просто сохранять координаты в БД с заданным временным интервалом.
Уже на этапе формулирования этой простой логики возникли проблемы. Код был написан 2 года назад, и было сложно вспомнить, что он делает, т.к. метод представлял собой просто очень длинное полотно.
Но когда удалось вспомнить и сформулировать назначение кода, дело пошло быстрей.
Во-первых, оказалось, что логика обращения к внешнему API и преобразования полученного ответа очень тесно переплетены. Вынесла обращение к API в отдельный класс. Теперь в будущем без больших проблем можно заменить его на другой, если потребуется.
Вынесла различные вспомогательные методы в отдельные классы, например логику работы с датами - в класс DateUtil. Теперь метод генерации практически соответствовал логике дизайна, не разбухая от мелких технических деталей.
Циклы заменила на декларативные stream-ы, это сделало код еще более наглядным.
В итоге новый код был написан по заранее сформулированному дизайну, а не по наитию, как старый код.
При этом старалась, чтобы из реализации максимально точно можно было бы воспроизвести дизайн.
И старый, и новый код делают одно и то же, но новый оказался намного более понятным и читаемым.
Как понять назначение системы?
Раньше думала, что надо внимательно изучить код, тогда вся система станет автоматически ясна.
Но оказалось, это неверно.
Нужно изучить именно дизайн, документацию. Дизайн - это как раз верхнеуровневое описание системы. Одному дизайну может соответствовать множество реализаций. Поэтому из кода и нельзя понять назначение системы. Но в идеале разработчику нужно стремиться к такой реализации, чтобы ее невозможно было сопоставить ни с каким другим дизайном.
Для закрепления этого принципа решила переписать несколько классов из моего пет-проекта.
Для начала выбрала класс, который представляет собой генератор списка случайных координат. Эти координаты потом используются для построения маршрута на UI. Логика довольно простая - есть метод, который принимает начальную и конечную координату поездки, максимальную скорость и время, с каким интервалом генерируются координаты. Для получения списка точек нужно обратиться к внешнему API, преобразовать результат, а затем просто сохранять координаты в БД с заданным временным интервалом.
Уже на этапе формулирования этой простой логики возникли проблемы. Код был написан 2 года назад, и было сложно вспомнить, что он делает, т.к. метод представлял собой просто очень длинное полотно.
Но когда удалось вспомнить и сформулировать назначение кода, дело пошло быстрей.
Во-первых, оказалось, что логика обращения к внешнему API и преобразования полученного ответа очень тесно переплетены. Вынесла обращение к API в отдельный класс. Теперь в будущем без больших проблем можно заменить его на другой, если потребуется.
Вынесла различные вспомогательные методы в отдельные классы, например логику работы с датами - в класс DateUtil. Теперь метод генерации практически соответствовал логике дизайна, не разбухая от мелких технических деталей.
Циклы заменила на декларативные stream-ы, это сделало код еще более наглядным.
В итоге новый код был написан по заранее сформулированному дизайну, а не по наитию, как старый код.
При этом старалась, чтобы из реализации максимально точно можно было бы воспроизвести дизайн.
И старый, и новый код делают одно и то же, но новый оказался намного более понятным и читаемым.
Liquibase и тестовые данные
Приступив к задаче в новом микросервисе, столкнулась с необходимостью сгенерировать много тестовых данных. Можно было бы по примеру уже готовых тестов написать init-скрипт или создать нужные entity и сохранить их прямо в тесте.
Но нашлось более универсальное решение - настроить liquibase для тестовых сценариев.
У нас уже были тесты на тестконтейнерах, и они использовали liquibase, как и само приложение. Вначале показалось, что будет достаточно просто создать тестовый чейнджлог и миграции с тестовыми данными. Но возникли сложности - оказалось, что все таблицы создаются в схеме public, хотя в БД они живут в отдельной схеме. Это вызывало ошибки.
Помимо того, на проекте есть тесты как для postgresql c тестконтейнерами, так и тесты для h2. А в моей задаче добавились миграции с использованием системных таблиц postgresql. И конечно, при добавлении новых миграций тесты для h2 упали.
Разобралась как настроить liquibase для работы с кастомной схемой, добавила 2 тестовых чейнджлога - для h2 и для postgres, настроила профайлы для работы с разными БД. В результате все тестовые данные можно использовать для всех тестов, а добавлять их стало быстрее и проще.
Приступив к задаче в новом микросервисе, столкнулась с необходимостью сгенерировать много тестовых данных. Можно было бы по примеру уже готовых тестов написать init-скрипт или создать нужные entity и сохранить их прямо в тесте.
Но нашлось более универсальное решение - настроить liquibase для тестовых сценариев.
У нас уже были тесты на тестконтейнерах, и они использовали liquibase, как и само приложение. Вначале показалось, что будет достаточно просто создать тестовый чейнджлог и миграции с тестовыми данными. Но возникли сложности - оказалось, что все таблицы создаются в схеме public, хотя в БД они живут в отдельной схеме. Это вызывало ошибки.
Помимо того, на проекте есть тесты как для postgresql c тестконтейнерами, так и тесты для h2. А в моей задаче добавились миграции с использованием системных таблиц postgresql. И конечно, при добавлении новых миграций тесты для h2 упали.
Разобралась как настроить liquibase для работы с кастомной схемой, добавила 2 тестовых чейнджлога - для h2 и для postgres, настроила профайлы для работы с разными БД. В результате все тестовые данные можно использовать для всех тестов, а добавлять их стало быстрее и проще.
👍1
В продолжение предыдущей заметки.
Написала юнит тесты к очередной задаче. Тестируемый метод A был приватным, поэтому в тестах пришлось вызывать публичный метод B, вызывающий А. Метод А делает выполняет некую логику и сохраняет результат в БД. Вроде бы все просто, надо дернуть В, и проверить что в БД оказались нужные данные. Но на деле оказалось, что метод B вызывает также методы C, D, E... которые требуют еще данные для собственных расчетов. Когда я замокала все побочные вызовы, код выглядел мягко говоря, не очень понятно.
Решила применить тот же подход, что в предыдущей заметке - сделать интеграционные тесты с testcontainers.
Настроила liquibase, добавила тестовые данные, замокала пару вызовов... Тесты прошли успешно и стали выглядеть гораздо чище. Но в консоли обнаружилось несколько ошибок.
Оказалось, что приложение использует несколько схем в БД, и одна регулярная таска, которая запускается при старте приложения, пытается получить данные таблиц из разных схем и падает. И хоть на работу теста это не влияет, но некрасиво оставлять такие грязные логи. К тому же, если в будущем тесты упадут, это может запутать разработчика, который будет фиксить багу.
Liquibase для работы с кастомными схемами уже был настроен. Дальше разобралась, как настроить несколько датасорсов для работы с разными схемами. Теперь тесты стали полностью эмулировать работу приложения в слое обращения к БД. И можно легко создавать более сложные интеграционные тестовые сценарии.
Написала юнит тесты к очередной задаче. Тестируемый метод A был приватным, поэтому в тестах пришлось вызывать публичный метод B, вызывающий А. Метод А делает выполняет некую логику и сохраняет результат в БД. Вроде бы все просто, надо дернуть В, и проверить что в БД оказались нужные данные. Но на деле оказалось, что метод B вызывает также методы C, D, E... которые требуют еще данные для собственных расчетов. Когда я замокала все побочные вызовы, код выглядел мягко говоря, не очень понятно.
Решила применить тот же подход, что в предыдущей заметке - сделать интеграционные тесты с testcontainers.
Настроила liquibase, добавила тестовые данные, замокала пару вызовов... Тесты прошли успешно и стали выглядеть гораздо чище. Но в консоли обнаружилось несколько ошибок.
Оказалось, что приложение использует несколько схем в БД, и одна регулярная таска, которая запускается при старте приложения, пытается получить данные таблиц из разных схем и падает. И хоть на работу теста это не влияет, но некрасиво оставлять такие грязные логи. К тому же, если в будущем тесты упадут, это может запутать разработчика, который будет фиксить багу.
Liquibase для работы с кастомными схемами уже был настроен. Дальше разобралась, как настроить несколько датасорсов для работы с разными схемами. Теперь тесты стали полностью эмулировать работу приложения в слое обращения к БД. И можно легко создавать более сложные интеграционные тестовые сценарии.
🔥4
Решила освежить знания по паттернам ООП. Прочитала книгу Head First "Паттерны проектирования". Оказалась очень интересной. Действие происходит в городке Объектвилле, и все аспекты его жизни управляются паттернами —
пиццерия, шоколадная фабрика, метеорологическая станция и т.д.
Пару лет назад читала классическую GOF -- Design Patterns. Если их сравнивать, то GOF воспринимается как энциклопедия, а Head First скорее как художественный роман.
Теперь обе книги у меня в библиотеке.
пиццерия, шоколадная фабрика, метеорологическая станция и т.д.
Пару лет назад читала классическую GOF -- Design Patterns. Если их сравнивать, то GOF воспринимается как энциклопедия, а Head First скорее как художественный роман.
Теперь обе книги у меня в библиотеке.
🔥3👍1
В книге Head First "Паттерны Проектирования" не рассказывается про один из известных паттернов - Visitor (Посетитель). Он используется, когда мы хотим добавить новое поведение классам из иерархии, но не хотим сильно править сами классы.
Допустим у нас в проекте есть иерархия спортсменов различного уровня. Поступило требование, чтобы каждый спортсмен сдавал норматив по отжиманиям и подтягиваниям.
Для нас может быть нежелательно создавать новые методы в иерархии. Например, наши классы удачно спроектированы, покрыты тестами и мы не хотим ничего сломать.
К тому же, при внесении новых методов может нарушаться Single Responsibility Principle. Мы хотим сконцентрировать в классах код, который описывает занятие конкретным видом спорта, а нормативы - это просто побочная рутина спортсмена. К тому же вполне возможно что нормативы будут меняться, и каждый раз придется править классы иерархии.
С помощью Посетителя мы можем отделить новое поведение от данных.
А еще это можно сделать, используя дефолтные методы интерфейсов в Java.
Дефолтные методы - это реализация концепции примеси.
Примесь (mix in) - элемент языка программирования, реализующий выделенное поведение. Позволяет дополнить классы новым поведением, без необходимости наследовать класс от данного элемента.
Пост с примерами кода:
https://dmatveeva.github.io/visitor-default-method/
Допустим у нас в проекте есть иерархия спортсменов различного уровня. Поступило требование, чтобы каждый спортсмен сдавал норматив по отжиманиям и подтягиваниям.
Для нас может быть нежелательно создавать новые методы в иерархии. Например, наши классы удачно спроектированы, покрыты тестами и мы не хотим ничего сломать.
К тому же, при внесении новых методов может нарушаться Single Responsibility Principle. Мы хотим сконцентрировать в классах код, который описывает занятие конкретным видом спорта, а нормативы - это просто побочная рутина спортсмена. К тому же вполне возможно что нормативы будут меняться, и каждый раз придется править классы иерархии.
С помощью Посетителя мы можем отделить новое поведение от данных.
А еще это можно сделать, используя дефолтные методы интерфейсов в Java.
Дефолтные методы - это реализация концепции примеси.
Примесь (mix in) - элемент языка программирования, реализующий выделенное поведение. Позволяет дополнить классы новым поведением, без необходимости наследовать класс от данного элемента.
Пост с примерами кода:
https://dmatveeva.github.io/visitor-default-method/
👍2
Протокол FIX и библиотека QuickFIX/J
В последнее время много разбиралась с протоколом FIX и библиотекой QuickFIX/J.
FIX (Financial Information eXchange) — это стандартный язык для обмена финансовой информацией между участниками рынка. Он используется для передачи заявок на покупку и продажу, котировок и прочего. FIX обеспечивает быструю и надежную коммуникацию, что делает его основным инструментом в финансовой сфере.
Для работы с протоколом FIX существует множество библиотек, одной из самых популярных является QuickFIX/J. Это открытая реализация FIX на языке Java, которая позволяет разработчикам создавать торговые приложения, взаимодействующие с различными торговыми системами.
Как подключить?
Если у вас проект на Maven:
Вот пример простого клиента, который подключается к серверу FIX и отправляет тестовый запрос:
- стандартизация: FIX является общепринятым стандартом для финансовых сообщений.
- open-source: не нужно платить за библиотеку.
- гибкость: можно настроить всё под свои нужды.
В последнее время много разбиралась с протоколом FIX и библиотекой QuickFIX/J.
FIX (Financial Information eXchange) — это стандартный язык для обмена финансовой информацией между участниками рынка. Он используется для передачи заявок на покупку и продажу, котировок и прочего. FIX обеспечивает быструю и надежную коммуникацию, что делает его основным инструментом в финансовой сфере.
Для работы с протоколом FIX существует множество библиотек, одной из самых популярных является QuickFIX/J. Это открытая реализация FIX на языке Java, которая позволяет разработчикам создавать торговые приложения, взаимодействующие с различными торговыми системами.
Как подключить?
Если у вас проект на Maven:
<dependency>Дальше нужно настроить конфиг (например, config.cfg):
<groupId>org.quickfixj</groupId>
<artifactId>quickfixj-core</artifactId>
<version>2.3.2</version>
</dependency>
[DEFAULT]Это базовые настройки: кто вы (SenderCompID), с кем общаетесь (TargetCompID) и куда подключаетесь.
ConnectionType=initiator
SocketConnectHost=your.host.ru
SocketConnectPort=9876
SenderCompID=SENDER_ID
TargetCompID=TARGET_ID
[SESSION]
BeginString=FIX.4.4
Вот пример простого клиента, который подключается к серверу FIX и отправляет тестовый запрос:
public class FixClient implements Application {
public void onLogon(SessionID sessionId) {
System.out.println("Успешный логин: " + sessionId);
sendTestRequest();
}
private void sendTestRequest() {
try {
Message testRequest = new Message();
testRequest.getHeader().setField(new MsgType(MsgType.TEST_REQUEST));
testRequest.setField(new TestReqID("Тест"));
Session.sendToTarget(testRequest);
} catch (SessionNotFound e) {
e.printStackTrace();
}
}
}
Если разобраться с конфигами и самим протоколом FIX, то это удобный инструмент для автоматизации торговли и интеграции с биржами или брокерами. Его плюсы:- стандартизация: FIX является общепринятым стандартом для финансовых сообщений.
- open-source: не нужно платить за библиотеку.
- гибкость: можно настроить всё под свои нужды.
TDD
Последние 3 месяца активно практикую TDD в своей работе.
В этом посте опишу свой опыт.
📌 По классике процесс разработки с TDD должен выглядеть так: написание короткого теста, который падает, затем написание минимального кода, чтобы тест прошел, и затем рефакторинг написанного кода, без добавления новой логики. В идеале такие тесты, написанные перед началом разработки, должны служить своеобразной спецификацией, а код в результате все время следует этой спецификации.
〰️ Лично мне было немного сложновато следовать классической схеме, часто слишком увлекаюсь написанием кода и изменения в итерациях не всегда получаются минималистичными.
〰️ Также очень часто перед началом разработки хочется написать не короткий тест, а сразу большой, чтобы охватить все этапы тестового сценария, пока общая картина после чтения документации еще свежа и не размылась от погружения в технические детали. Но при следовании TDD, написание и выполнение теста должны быть быстрыми. Это значит что надо писать юнит-тесты, а в нашем проекте в основном используются интеграционные. Да и сама система спроектирована так, что сложно писать юнит-тесты, очень много приватных методов, а публичные часто принимают сложные объекты, завязанные на другие объекты.
〰️ Все это приводит к тому, что когда иногда интеграционных тестов становится слишком много, и они выполняются слишком долго. Пока размышляю над тем, как все-таки частично перейти на юнит-тесты.
Но несмотря на это, плюсов у TDD для меня оказалось намного больше.
✔️Во-первых, теперь к каждой задаче пишу тесты, что улучшает покрытие.
✔️Во-вторых, раньше зачастую недооценивала для себя время, нужное для написания тестов, и это выливалось в неверное планирование сроков. Теперь временные затраты оцениваю куда лучше.
✔️В-третьих, когда тест пишется после кода, сложно абстрагироваться для написания максимально непредвзятого теста. Все равно неосознанно опираешься на написанный код, и легко упустить краевые случаи.
✔️В-четвертых, появляется большая свобода в маневрах. Если по ходу разработки я хочу переписать свой код, то могу просто откатиться к предыдущему коммиту (которые надо делать после каждой итерации) и начать все заново. Не боясь, что большой кусок кода будет невозвратно сломан.
Подводя итог, для меня это крайне положительный опыт и очень продуктивная техника тестирования, которой буду придерживаться и дальше.
Последние 3 месяца активно практикую TDD в своей работе.
В этом посте опишу свой опыт.
📌 По классике процесс разработки с TDD должен выглядеть так: написание короткого теста, который падает, затем написание минимального кода, чтобы тест прошел, и затем рефакторинг написанного кода, без добавления новой логики. В идеале такие тесты, написанные перед началом разработки, должны служить своеобразной спецификацией, а код в результате все время следует этой спецификации.
〰️ Лично мне было немного сложновато следовать классической схеме, часто слишком увлекаюсь написанием кода и изменения в итерациях не всегда получаются минималистичными.
〰️ Также очень часто перед началом разработки хочется написать не короткий тест, а сразу большой, чтобы охватить все этапы тестового сценария, пока общая картина после чтения документации еще свежа и не размылась от погружения в технические детали. Но при следовании TDD, написание и выполнение теста должны быть быстрыми. Это значит что надо писать юнит-тесты, а в нашем проекте в основном используются интеграционные. Да и сама система спроектирована так, что сложно писать юнит-тесты, очень много приватных методов, а публичные часто принимают сложные объекты, завязанные на другие объекты.
〰️ Все это приводит к тому, что когда иногда интеграционных тестов становится слишком много, и они выполняются слишком долго. Пока размышляю над тем, как все-таки частично перейти на юнит-тесты.
Но несмотря на это, плюсов у TDD для меня оказалось намного больше.
✔️Во-первых, теперь к каждой задаче пишу тесты, что улучшает покрытие.
✔️Во-вторых, раньше зачастую недооценивала для себя время, нужное для написания тестов, и это выливалось в неверное планирование сроков. Теперь временные затраты оцениваю куда лучше.
✔️В-третьих, когда тест пишется после кода, сложно абстрагироваться для написания максимально непредвзятого теста. Все равно неосознанно опираешься на написанный код, и легко упустить краевые случаи.
✔️В-четвертых, появляется большая свобода в маневрах. Если по ходу разработки я хочу переписать свой код, то могу просто откатиться к предыдущему коммиту (которые надо делать после каждой итерации) и начать все заново. Не боясь, что большой кусок кода будет невозвратно сломан.
Подводя итог, для меня это крайне положительный опыт и очень продуктивная техника тестирования, которой буду придерживаться и дальше.
❤4✍3
Разобраться с кодовой базой
Работая на текущем проекте около полугода, задумалась, как эффективно разбираться с все более возрастающей кодовой базой. Под ответственность нашей команды передаются новые микросервисы, и часто новая задача = новый микросервис = новый процесс разбирательства с кодом с нуля. Простое чтение незнакомого кода и документации не позволяло качественно ускорить погружение в контекст и разработку новых фич.
Вот несколько шагов, которые помогли мне:
knowledge sharing по архитектуре
Смысл в том, что разработчик, который несколько знаком с сервисом, верхнеуровнево рассказывает о его процессах, об основных абстракциях, о потоках данных и преобразованиях с ними.
Плюсы таких встреч:
+ Рассказчик во время подготовки и объяснения улучшает свое понимание, обращает внимания на разные нюансы кода.
+ Слушатели узнают про архитектуру незнакомого микросервиса.
+ Тот, кто знаком близко с кодом данного сервиса, может внести правки или подсветить нетривиальные моменты.
В нашей команде была проведена серия таких встреч, и для меня это было чрезвычайно полезно.
Помимо рассказа, коллеги поделились диаграммами и схемами, которые тоже очень сильно помогают продвинуться в понимании .
Вопросы аналитикам
У нас ведется документация, но мне как разработчику часто не хватает полного видения.
Например, начиная разработку, я читаю в спецификации, что микросервис X должен отсылать сообщение в таком-то формате микросервису Y.
Если отправка прошла успешно, то сделать то-то, если вернулась ошибка, то другое.
Я могу просто написать такой код, но я буду знать о нем?
- Буду ли я понимать, для чего нужны сервисы X и Y?
- Что за процесс, для которого требуется отправка сообщения?
- Если мы получили положительный ответ, какие процессы начнутся в сервисе Y?
- Гарантирует ли положительный ответ то, что процессы в Y завершились успешно или лишь успешную доставку сообщения в Y? А почему?
Но найти ответы на эти вопросы в документации зачастую нетривиально. Они могут быть раскидана по разным разделам и страницам.
А поговорив с аналитиком, можно быстро получить информацию.
Конспект
После того, как получаю ответы на свои вопросы, стараюсь по возможности оформить их как конспект и сохранить в документации.
Плюсы такого подхода:
+ Информация лучше закрепляется
+ При необходимости легко вернуться и вспомнить что-то
+ Может быть полезно другим членам команды, особенно при онбординге
+ Сильно сокращает время ознакомления с информацией, т.к. часовое видео можно иногда уместить на пару страниц.
Все шаги были направлены на то, чтобы разобраться с кодом верхнеуровнево - получить целостное представление о системе, о процессах и преобразованиях данных в ней. С таким пониманием уже намного проще разбираться с конкретными классами, легко встраивать код в существующую систему и добавлять новый функционал.
Работая на текущем проекте около полугода, задумалась, как эффективно разбираться с все более возрастающей кодовой базой. Под ответственность нашей команды передаются новые микросервисы, и часто новая задача = новый микросервис = новый процесс разбирательства с кодом с нуля. Простое чтение незнакомого кода и документации не позволяло качественно ускорить погружение в контекст и разработку новых фич.
Вот несколько шагов, которые помогли мне:
knowledge sharing по архитектуре
Смысл в том, что разработчик, который несколько знаком с сервисом, верхнеуровнево рассказывает о его процессах, об основных абстракциях, о потоках данных и преобразованиях с ними.
Плюсы таких встреч:
+ Рассказчик во время подготовки и объяснения улучшает свое понимание, обращает внимания на разные нюансы кода.
+ Слушатели узнают про архитектуру незнакомого микросервиса.
+ Тот, кто знаком близко с кодом данного сервиса, может внести правки или подсветить нетривиальные моменты.
В нашей команде была проведена серия таких встреч, и для меня это было чрезвычайно полезно.
Помимо рассказа, коллеги поделились диаграммами и схемами, которые тоже очень сильно помогают продвинуться в понимании .
Вопросы аналитикам
У нас ведется документация, но мне как разработчику часто не хватает полного видения.
Например, начиная разработку, я читаю в спецификации, что микросервис X должен отсылать сообщение в таком-то формате микросервису Y.
Если отправка прошла успешно, то сделать то-то, если вернулась ошибка, то другое.
Я могу просто написать такой код, но я буду знать о нем?
- Буду ли я понимать, для чего нужны сервисы X и Y?
- Что за процесс, для которого требуется отправка сообщения?
- Если мы получили положительный ответ, какие процессы начнутся в сервисе Y?
- Гарантирует ли положительный ответ то, что процессы в Y завершились успешно или лишь успешную доставку сообщения в Y? А почему?
Но найти ответы на эти вопросы в документации зачастую нетривиально. Они могут быть раскидана по разным разделам и страницам.
А поговорив с аналитиком, можно быстро получить информацию.
Конспект
После того, как получаю ответы на свои вопросы, стараюсь по возможности оформить их как конспект и сохранить в документации.
Плюсы такого подхода:
+ Информация лучше закрепляется
+ При необходимости легко вернуться и вспомнить что-то
+ Может быть полезно другим членам команды, особенно при онбординге
+ Сильно сокращает время ознакомления с информацией, т.к. часовое видео можно иногда уместить на пару страниц.
Все шаги были направлены на то, чтобы разобраться с кодом верхнеуровнево - получить целостное представление о системе, о процессах и преобразованиях данных в ней. С таким пониманием уже намного проще разбираться с конкретными классами, легко встраивать код в существующую систему и добавлять новый функционал.
👍2❤1
Spring State Machine
Недавно в нашем проекте появился новый микросервис. После начальной конфигурации и добавления минимального функционала он стал быстро обрастать бизнес-логикой -- создание продуктов, процессинг, перевод продукта из статуса в статус. В других наших микросервисах за это отвечает непосредственно бизнес-логика, но для нового сервиса аналитики предложили внедрить Spring State Machine.
Из определения SSM:
"Spring State Machine — это фреймворк для Spring-приложений, который реализует концепцию расширенного конечного автомата. Он позволяет декларативно определять состояния и переходы между ними, поддерживает иерархические и параллельные состояния, а также действия, условия перехода и события."
Это показалось хорошей идеей: возможность формализовать логику переходов между состояниями и уйти от запутанных переходов в коде с многочисленными if...else if конструкциями. Задача ушла в разработку.
Когда я просматривала ее на code review, меня заинтересовал один технический момент. Пока я ресерчила этот вопрос, я внезапно наткнулась на сообщение о том, что Spring прекращает поддержку open source версии Spring State Machine с июня 2026! А значит через год библиотека SSM устареет и обновлений не предвидится. Но политика нашей компании обязывает обновлять версии, в которых обнаружены уязвимости. А поскольку обновлений нет, то библиотеку пришлось бы убирать из проекта.
А сколько кода было бы написано через год поверх SSM в нашем сервисе? И как больно было бы выпиливать его? Конечно, было принято решение не внедрять SSM в проект.
Для меня было сюрпризом, что Spring просто прекратил поддержку одного из своих проектов. Анонс о прекращении был 21 апреля 2025 года : https://spring.io/blog/2025/04/21/spring-cloud-data-flow-commercial
И что мне показалось интересно, я встретила свежие статьи в рунете с разбором SSM, которые даже не упоминали факт, что через год библиотека станет неактуальной. Похоже, стоит перепроверять на Github активность по каждой third-party библиотеке, которая добавляется в проект.
#рабочее
Недавно в нашем проекте появился новый микросервис. После начальной конфигурации и добавления минимального функционала он стал быстро обрастать бизнес-логикой -- создание продуктов, процессинг, перевод продукта из статуса в статус. В других наших микросервисах за это отвечает непосредственно бизнес-логика, но для нового сервиса аналитики предложили внедрить Spring State Machine.
Из определения SSM:
"Spring State Machine — это фреймворк для Spring-приложений, который реализует концепцию расширенного конечного автомата. Он позволяет декларативно определять состояния и переходы между ними, поддерживает иерархические и параллельные состояния, а также действия, условия перехода и события."
Это показалось хорошей идеей: возможность формализовать логику переходов между состояниями и уйти от запутанных переходов в коде с многочисленными if...else if конструкциями. Задача ушла в разработку.
Когда я просматривала ее на code review, меня заинтересовал один технический момент. Пока я ресерчила этот вопрос, я внезапно наткнулась на сообщение о том, что Spring прекращает поддержку open source версии Spring State Machine с июня 2026! А значит через год библиотека SSM устареет и обновлений не предвидится. Но политика нашей компании обязывает обновлять версии, в которых обнаружены уязвимости. А поскольку обновлений нет, то библиотеку пришлось бы убирать из проекта.
А сколько кода было бы написано через год поверх SSM в нашем сервисе? И как больно было бы выпиливать его? Конечно, было принято решение не внедрять SSM в проект.
Для меня было сюрпризом, что Spring просто прекратил поддержку одного из своих проектов. Анонс о прекращении был 21 апреля 2025 года : https://spring.io/blog/2025/04/21/spring-cloud-data-flow-commercial
И что мне показалось интересно, я встретила свежие статьи в рунете с разбором SSM, которые даже не упоминали факт, что через год библиотека станет неактуальной. Похоже, стоит перепроверять на Github активность по каждой third-party библиотеке, которая добавляется в проект.
#рабочее
👀1
Недавно столкнулась с ситуацией — один из сервисов на тестовом стенде не вычитывал сообщения из топика кафки.
Я убедилась, что сообщения точно попадают в топик, и что сервис вычитывает некоторые. Но не все.
И тут я заметила, что из топика почему-то читают пять консьюмеров, хотя экземпляров сервиса на тестовом стенде у нас всего три. Откуда еще два?🧐
Оказалось, что при обновлении config map для dev стенда прописали те же параметры кафки, что и для тестового стенда, и это экземпляры сервиса на dev стенде "крали" сообщения из топика.
Я убедилась, что сообщения точно попадают в топик, и что сервис вычитывает некоторые. Но не все.
И тут я заметила, что из топика почему-то читают пять консьюмеров, хотя экземпляров сервиса на тестовом стенде у нас всего три. Откуда еще два?🧐
Оказалось, что при обновлении config map для dev стенда прописали те же параметры кафки, что и для тестового стенда, и это экземпляры сервиса на dev стенде "крали" сообщения из топика.
😁3✍2
Обнаружила еще один плюс TDD.
Согласно подходу, я пишу тест, который падает, пишу код, чтобы тест прошел, и делаю коммит. Коммиты стараюсь делать как можно чаще. И обычно во время разработки я запускаю только этот тест, который пишу в данный момент, т.к. каждый раз прогонять абсолютно все тесты в проекте долго, особенно если там много интеграционных. Целиком тесты я запускаю только время от времени, если задача большая. Либо уже по окончании разработки перед пушем, если не очень большая.
И в какой-то момент по завершении фичи часть тестов упала, хотя я их даже не трогала. Благодаря TDD я быстро и без усилий обнаружила причину — просто откатила последовательно к каждому коммиту, и нашла, на каком эти тесты сломались. А т.к. коммиты относительно небольшие, проблему было легко локализовать.
Согласно подходу, я пишу тест, который падает, пишу код, чтобы тест прошел, и делаю коммит. Коммиты стараюсь делать как можно чаще. И обычно во время разработки я запускаю только этот тест, который пишу в данный момент, т.к. каждый раз прогонять абсолютно все тесты в проекте долго, особенно если там много интеграционных. Целиком тесты я запускаю только время от времени, если задача большая. Либо уже по окончании разработки перед пушем, если не очень большая.
И в какой-то момент по завершении фичи часть тестов упала, хотя я их даже не трогала. Благодаря TDD я быстро и без усилий обнаружила причину — просто откатила последовательно к каждому коммиту, и нашла, на каком эти тесты сломались. А т.к. коммиты относительно небольшие, проблему было легко локализовать.