SemVer и как меня расстроил JUnit 6
Эта осень богата на релизы. Недавно вышла Java 25, в ноябре выйдет Spring 7 и Spring Boot 4. Другие библиотеки тоже выпускают обновления. Сегодня обратим внимание на два камбэка:
✨ JUnit 6. Пятая версия вышла 8 лет назад, в 2017 году
✨ Jackson 3. Jackson - верный помощник в перекладывании джейсонов, вторая версия вышла больше 10 лет назад
Когда выходит новая версия после большого перерыва, душа трепетно ждёт серьезных изменений. Потом смотришь release notes, а там все скучно. Что-то переименовали, что-то удалили, повысили версию джавы😒
Но если нет ничего нового, зачем повышать версию?
Ответ прост. На большинстве проектов используется семантическое версионирование (SemVer) - популярное соглашение о формате версий. Оно задаёт формат MAJOR.MINOR.PATCH, где
▫️ MAJOR — мажорная версия, меняется при несовместимых изменениях API
▫️ MINOR — минорная версия, добавляет функциональность с обратной совместимостью
▫️ PATCH — меняется при исправлении багов
Дополнительно в версии может быть номер билда, целевой стенд или пользователь, который запустил сборку. Может быть префикс/суффикс/тег, чтобы подчеркнуть особый статус билда. Например, у сборок Spring
🌸 6.2.0-RC3 - release candidate. Билд с зафиксированным набором фич для интенсивного тестирования
🌸 7.0.0-M9. М значит Milestone, большие изменения для сбора обратной связи и тестирования
Короче, мажорная версия не обязательно означает новые фичи и технологическую эволюцию. Это значит, что новая версия несовместима по апи со старой. Поменялся интерфейс, изменились имена классов - всё это примеры несовместимых изменений.
Но это теория. От ожиданий никуда не деться. Даже за пределами IT люди ждут значимых изменений от мажорных версий. Новый айфон, новая модель нейронки. Я вот расстроилась, что в JUnit 6 не завезли ничего интересного💔
Ну да ладно. Зачем нужно знание SemVer на практике?
Чтобы планировать масштаб работ для обновления. При апдейте спринга с 6.1 на 6.2 скорее всего проблем не будет. А вот переход 6.2 -> 7 может затянуться. Кто обновлял Spring Boot со второй версии на третью и менял тысячу импортов - жмите ❤️. Переход Jackson 2 -> Jackson 3 очень похож, основная работа при апдейте - это замена com.fasterxml.jackson на tools.jackson.
Если в JUnit 6 и Jackson 3 ничего особенного, зачем обновляться?
Потому что Spring 7 использует эти новые версии. Если захотите обновить спринг, придётся обновить и эти библиотеки. Так что это неизбежно🌚
Эта осень богата на релизы. Недавно вышла Java 25, в ноябре выйдет Spring 7 и Spring Boot 4. Другие библиотеки тоже выпускают обновления. Сегодня обратим внимание на два камбэка:
✨ JUnit 6. Пятая версия вышла 8 лет назад, в 2017 году
✨ Jackson 3. Jackson - верный помощник в перекладывании джейсонов, вторая версия вышла больше 10 лет назад
Когда выходит новая версия после большого перерыва, душа трепетно ждёт серьезных изменений. Потом смотришь release notes, а там все скучно. Что-то переименовали, что-то удалили, повысили версию джавы😒
Но если нет ничего нового, зачем повышать версию?
Ответ прост. На большинстве проектов используется семантическое версионирование (SemVer) - популярное соглашение о формате версий. Оно задаёт формат MAJOR.MINOR.PATCH, где
▫️ MAJOR — мажорная версия, меняется при несовместимых изменениях API
▫️ MINOR — минорная версия, добавляет функциональность с обратной совместимостью
▫️ PATCH — меняется при исправлении багов
Дополнительно в версии может быть номер билда, целевой стенд или пользователь, который запустил сборку. Может быть префикс/суффикс/тег, чтобы подчеркнуть особый статус билда. Например, у сборок Spring
🌸 6.2.0-RC3 - release candidate. Билд с зафиксированным набором фич для интенсивного тестирования
🌸 7.0.0-M9. М значит Milestone, большие изменения для сбора обратной связи и тестирования
Короче, мажорная версия не обязательно означает новые фичи и технологическую эволюцию. Это значит, что новая версия несовместима по апи со старой. Поменялся интерфейс, изменились имена классов - всё это примеры несовместимых изменений.
Но это теория. От ожиданий никуда не деться. Даже за пределами IT люди ждут значимых изменений от мажорных версий. Новый айфон, новая модель нейронки. Я вот расстроилась, что в JUnit 6 не завезли ничего интересного💔
Ну да ладно. Зачем нужно знание SemVer на практике?
Чтобы планировать масштаб работ для обновления. При апдейте спринга с 6.1 на 6.2 скорее всего проблем не будет. А вот переход 6.2 -> 7 может затянуться. Кто обновлял Spring Boot со второй версии на третью и менял тысячу импортов - жмите ❤️. Переход Jackson 2 -> Jackson 3 очень похож, основная работа при апдейте - это замена com.fasterxml.jackson на tools.jackson.
Если в JUnit 6 и Jackson 3 ничего особенного, зачем обновляться?
Потому что Spring 7 использует эти новые версии. Если захотите обновить спринг, придётся обновить и эти библиотеки. Так что это неизбежно🌚
❤169🔥36👍32👎6
Распределенный лок
Часто коллеги приходят с идеей сделать что-то через распределенный лок. В этом посте расскажу, почему эта задача сложнее, чем кажется, какие подводные камни встречаются и возможные альтернативы.
Небольшое интро.
Распределенный лок помогает сервисам "поделить" какой-то ресурс. Ресурсом может быть задача, обработка файла или какой-то сущности. Сам лок не контролирует доступ к ресурсу, это лишь способ договориться. Кто захватил лок, тот и работает с ресурсом.
В чем сложность работы с распределенным локом?
В комбинации "сложный алгоритм + общение по сети". Работа с локом — это не просто "взял-отпустил". Полный цикл выглядит так:
Создать лок -> Попытаться захватить -> Если не получилось: попробовать ещё раз или встать в очередь -> Отпустить -> Удалить
Каждый участник в любой момент может отвалиться, а запрос - задержаться. В итоге получаем мешок вопросов, которые нужно обдумать:
Многие вопросы снимаются инструментами, но не все. Race condition в распределенных системах встречается сплошь и рядом. Взять хотя бы недавний сбой Амазона, где всё началось с того, что 2 сервера одновременно накатывали апдейт и помешали друг другу.
Ещё одна проблема с локами - тестирование. В большинстве случаев разработчик проверит вручную пару кейсов с помощью Thread.sleep. Но это детский сад, конечно. Написать автоматизированные тесты для распределенных локов очень сложно.
Поэтому даже если система маленькая, всё крутится на одном сервере и сетевые проблемы сведены к минимуму, рекомендую рассмотреть альтернативы. Например
✔️ Сделать задачу идемпотентной и безопасной для многократного выполнения
✔️ Провернуть Inversion of control. Ресурсы распределяются по исполнителям, а не исполнители борются за ресурсы
Оба подхода можно протестировать, и общая логика часто упрощается. Берите на заметку, квинтэссенция многолетнего опыта:)
Но если сердце не видит преград, и сделать лок хочется, вот пара заметок:
💫 Лок можно реализовать на Postgres (SELECT … FOR UPDATE), Redis, Zookeeper, Kubernetes. Гляньте библиотеку ShedLock
💫 ID держателя лока удобно записывать в лок. ID каждого участника должен быть постоянным
💫 Вместо плясок с TTL можно положиться на связь сервиса и лока. В Zookeeper есть ephemeral nodes, которые исчезают, если связь с сервисом пропадает. Транзакция в Postgres может не сразу обнаружить разрыв соединения, но тоже в итоге откатится
Что почитать:
🔥 Cтатья Alibaba Cloud 2024 года. Обзор решений на джаве и их нюансов
🔥 Статья Клепмана (автор книги с кабанчиком) про недостатки распределенного лока в Redis . Статья старая (2016 год) и специфичная, но полезна для полноты картины
Часто коллеги приходят с идеей сделать что-то через распределенный лок. В этом посте расскажу, почему эта задача сложнее, чем кажется, какие подводные камни встречаются и возможные альтернативы.
Небольшое интро.
Распределенный лок помогает сервисам "поделить" какой-то ресурс. Ресурсом может быть задача, обработка файла или какой-то сущности. Сам лок не контролирует доступ к ресурсу, это лишь способ договориться. Кто захватил лок, тот и работает с ресурсом.
В чем сложность работы с распределенным локом?
В комбинации "сложный алгоритм + общение по сети". Работа с локом — это не просто "взял-отпустил". Полный цикл выглядит так:
Создать лок -> Попытаться захватить -> Если не получилось: попробовать ещё раз или встать в очередь -> Отпустить -> Удалить
Каждый участник в любой момент может отвалиться, а запрос - задержаться. В итоге получаем мешок вопросов, которые нужно обдумать:
Что делать, если сервис взял лок, но умер?
Что будет, если один сервис отправит 2 команды захватить лок?
Может ли сервис отпустить лок, который он не держит?
Что делать, если порядок запросов нарушится, и сначала на лок пришла команда "отпустить", а потом "взять"?
Кто будет создавать локи?
Будет ли атомарно работать связка "создать и захватить лок"?
Сколько сервис будет пытаться захватить лок? С какими интервалами?
Если сервис встаёт в очередь к локу - сколько времени ждать? можно ли выйти из очереди?
Кто и когда будет удалять локи?
Многие вопросы снимаются инструментами, но не все. Race condition в распределенных системах встречается сплошь и рядом. Взять хотя бы недавний сбой Амазона, где всё началось с того, что 2 сервера одновременно накатывали апдейт и помешали друг другу.
Ещё одна проблема с локами - тестирование. В большинстве случаев разработчик проверит вручную пару кейсов с помощью Thread.sleep. Но это детский сад, конечно. Написать автоматизированные тесты для распределенных локов очень сложно.
Поэтому даже если система маленькая, всё крутится на одном сервере и сетевые проблемы сведены к минимуму, рекомендую рассмотреть альтернативы. Например
✔️ Сделать задачу идемпотентной и безопасной для многократного выполнения
✔️ Провернуть Inversion of control. Ресурсы распределяются по исполнителям, а не исполнители борются за ресурсы
Оба подхода можно протестировать, и общая логика часто упрощается. Берите на заметку, квинтэссенция многолетнего опыта:)
Но если сердце не видит преград, и сделать лок хочется, вот пара заметок:
💫 Лок можно реализовать на Postgres (SELECT … FOR UPDATE), Redis, Zookeeper, Kubernetes. Гляньте библиотеку ShedLock
💫 ID держателя лока удобно записывать в лок. ID каждого участника должен быть постоянным
💫 Вместо плясок с TTL можно положиться на связь сервиса и лока. В Zookeeper есть ephemeral nodes, которые исчезают, если связь с сервисом пропадает. Транзакция в Postgres может не сразу обнаружить разрыв соединения, но тоже в итоге откатится
Что почитать:
🔥 Cтатья Alibaba Cloud 2024 года. Обзор решений на джаве и их нюансов
🔥 Статья Клепмана (автор книги с кабанчиком) про недостатки распределенного лока в Redis . Статья старая (2016 год) и специфичная, но полезна для полноты картины
🔥82👍32❤26👎3
Value types: основное
На прошлой неделе вышел билд с реализацией value types и основными оптимизациями. Это даже не превью версия, детали ещё поменяются, но первое впечатление составить уже можно. Вся информация есть в JEP 401.
Сегодня расскажу основные технические моменты, отдельным постом напишу мнение про концепт идентичности, на базе которого стоит весь value тип.
Интро
В java 2гендера типа сущностей — примитивы и ссылочные типы. К первой группе относятся int, long, boolean и тд. В таких переменных хранится само значение. Набор действий с примитивами ограничен, зато вычисления происходят с космической скоростью.
К ссылочным типам относятся классы, массивы, интерфейсы и тд. Они хранят указатель на участок памяти, где находится объект. Классы содержат поля и методы, работать с ними приятнее, чем с набором чисел. Но есть минус - при работе с объектами нужно постоянно прыгать по памяти.
Цель value типов - взять лучшее из двух миров: удобство классов и скорость примитивов. Добавляется ключевое слово value, которое ставится перед классом:
Поля класса становятся final, сам класс тоже final. Но главное, что в памяти список LocalDate будет лежать плоско, без лишних заголовков и прыжков по куче.
Глядите на картинку👇 Слева список объектов, справа - набор value. Работа со списками обещает быть blazingly fast🚀
Value types прекрасно вписываются в текущие тренды. Сейчас через сервисы проходит море данных, которые в большинстве своём неизменяемые. Уплощение структуры даст буст в скорости обработки. Поэтому value types так ждут.
Модификатор value уже получили 30 базовых классов:
▫️ обёртки примитивов: Integer, Long, …
▫️ Optional*
▫️ классы дат: LocalDate, LocalDateTime, …
Классы выше - база, поэтому перфоманс подрастёт просто при переходе на JDK с value типами. Такое мы любим.
Ещё немного странного/интересного:
🤔 value можно добавить абстрактному классу. Тогда наследники тоже станут value классами. В JEP написано, что наследники могут отказаться от value модификатора, но как - непонятно:)
🤔 value class vs record
Record - final класс с final полями, идеальный кандидат, чтобы стать value классом по умолчанию. Но это не так, для records нужно явно прописывать value. В самом JEP объяснение сводится к утверждению
Что, безусловно, верно. Но непонятно, почему неверно обратное. Остаётся только додумывать про обратную совместимость с уже существующими рекордс
🤔 String не стал value классом
Опять же, могу найти техническую причину. Хэш строки вычисляется лениво, а в value классе поля должны быть final. Здесь помогла бы другая фича, которая сейчас в превью — StableValue, ленивая инициализация final полей.
Но в JEP пишут, что дело в наличии идентичности (identity) у строки. Про концепт идентичности напишу отдельный пост, но если кратко: identity определяет возможность сравнения 2 объектов с одинаковыми значениями.
Почему у строк есть identity, мне непонятно. Пул строк, дедупликация и прочие оптимизации явно не уважают право String на идентичность.
Это только основное, на что точно следует обратить внимание. В тексте ещё очень много интересного, куча намёков на будущие фичи и оптимизации. Давно не читала JEP с таким интересом😊
На прошлой неделе вышел билд с реализацией value types и основными оптимизациями. Это даже не превью версия, детали ещё поменяются, но первое впечатление составить уже можно. Вся информация есть в JEP 401.
Сегодня расскажу основные технические моменты, отдельным постом напишу мнение про концепт идентичности, на базе которого стоит весь value тип.
Интро
В java 2
К ссылочным типам относятся классы, массивы, интерфейсы и тд. Они хранят указатель на участок памяти, где находится объект. Классы содержат поля и методы, работать с ними приятнее, чем с набором чисел. Но есть минус - при работе с объектами нужно постоянно прыгать по памяти.
Цель value типов - взять лучшее из двух миров: удобство классов и скорость примитивов. Добавляется ключевое слово value, которое ставится перед классом:
value class LocalDate {
int year;
int month;
int day;
// конструкторы, методы
}Поля класса становятся final, сам класс тоже final. Но главное, что в памяти список LocalDate будет лежать плоско, без лишних заголовков и прыжков по куче.
Глядите на картинку👇 Слева список объектов, справа - набор value. Работа со списками обещает быть blazingly fast🚀
Value types прекрасно вписываются в текущие тренды. Сейчас через сервисы проходит море данных, которые в большинстве своём неизменяемые. Уплощение структуры даст буст в скорости обработки. Поэтому value types так ждут.
Модификатор value уже получили 30 базовых классов:
▫️ обёртки примитивов: Integer, Long, …
▫️ Optional*
▫️ классы дат: LocalDate, LocalDateTime, …
Классы выше - база, поэтому перфоманс подрастёт просто при переходе на JDK с value типами. Такое мы любим.
Ещё немного странного/интересного:
🤔 value можно добавить абстрактному классу. Тогда наследники тоже станут value классами. В JEP написано, что наследники могут отказаться от value модификатора, но как - непонятно:)
🤔 value class vs record
Record - final класс с final полями, идеальный кандидат, чтобы стать value классом по умолчанию. Но это не так, для records нужно явно прописывать value. В самом JEP объяснение сводится к утверждению
Не каждый value класс можно сделать record
Что, безусловно, верно. Но непонятно, почему неверно обратное. Остаётся только додумывать про обратную совместимость с уже существующими рекордс
🤔 String не стал value классом
Опять же, могу найти техническую причину. Хэш строки вычисляется лениво, а в value классе поля должны быть final. Здесь помогла бы другая фича, которая сейчас в превью — StableValue, ленивая инициализация final полей.
Но в JEP пишут, что дело в наличии идентичности (identity) у строки. Про концепт идентичности напишу отдельный пост, но если кратко: identity определяет возможность сравнения 2 объектов с одинаковыми значениями.
Почему у строк есть identity, мне непонятно. Пул строк, дедупликация и прочие оптимизации явно не уважают право String на идентичность.
Это только основное, на что точно следует обратить внимание. В тексте ещё очень много интересного, куча намёков на будущие фичи и оптимизации. Давно не читала JEP с таким интересом😊
🔥110👍54❤35👎2
Integer v1 = 2000; Integer v2 = 2000; Что напечатает System.out.println(v1 == v2)?
Anonymous Poll
22%
true
78%
false
Value types: концепт идентичности
Сегодня продолжу разбирать JEP 401: value types. Начало в прошлом посте.
В JEP слово identity встречается 87 раз, практически в каждом абзаце. В этом посте разберу концепт идентичности и что меня в нём смущает.
В систему типов вводится новый термин - идентичность (identity):
▫️ У примитивов идентичности нет. 10 в одном месте ничем не отличается от 10 в другом месте
▫️ У ссылочных типов идентичность есть, каждый объект уникален. 2 объекта UserDTO - это разные объекты, даже если поля одинаковые
▫️ У value классов идентичности нет
С первого взгляда кажется, что идентичность это про == и equals. Но по мере чтения JEP растёт ощущение, что что-то не так. Например
1️⃣ Нет чёткого определения идентичности
Понятия типа identity относятся больше к проектированию модели данных. Value object, агрегаты и прочий DDD.
Java — это всё ещё язык программирования уровня циклы/классы. Примитивы и ссылочные типы описываются техническими характеристиками - стек, куча, наличие методов и тд.
Идейно Identity — что-то про возможность сравнения объектов между собой. Но что это означает с технической точки зрения в java — непонятно.
2️⃣ Сравнение value типов описано в JEP очень противоречиво:
💁🏼 С одной стороны, много раз повторили, что == сравнивает value объекты по значению полей. Как в примитивах, 123 == 123
🙋🏼 Но для некоторых value объектов equals вернет true, а == вернет false. Поэтому жирным шрифтом рекомендуют использовать equals
value класс - это всё ещё класс. Маловероятно, что кто-то будет использовать == для сравнения объектов. И объяснение какое-то есть. Но осадочек, что тема мутная, всё равно остаётся.
🌚 Конспирологическая теория, зачем на самом деле нужна identity
Внятного определения идентичности нет, значит понятие само по себе не важно. Гораздо важнее, что такое "тип без индентичности". Здесь написано чётко. Тип без идентичности
▫️ Immutable: неизменяемый
▫️ Interchangeable: взаимозаменяемый. Необязательно различать две сущности, если у них одинаковые значения
Для сущностей с этими свойствами добавляется пачка оптимизаций. Помимо уплощения кучи в списках есть, например, такая:
Подобная идея лежит в основе пулов для базовых типов. При вызове Integer.valueOf(127) не создаётся новый объект, а берётся уже существующий из пула. Для классов-обёрток это существенная экономия.
Текущая цель модификатора value и концепта identity (вернее его отсутствия) - обозначить, к каким объектам можно применить пачку оптимизаций.
Новый тип и рассуждение про доменные сущности выглядит пока натянуто. Нужно явно обозначить определения, границы и ожидаемые эффекты от value. Типы данных - база, одна из первых тем при освоении языка. Должно быть простое объяснение, чем отличаются типы, без погружения в дебри возможных оптимизаций. Должно быть конкретное определение identity, если оно важно для выбора типа.
Годы работы и преподавания не проходят бесследно. Если что-то может быть понятно неверно - оно будет понято неверно💯 Поэтому пока оцениваю новую фичу на троечку
И ответ на вопрос перед постом. Сейчас в консоли будет false, объекты сравниваются по ссылке. Возможно, после введения value types тот же код вернёт true, и популярный вопрос с собеседований получит вторую жизнь. Но может и нет💅
Сегодня продолжу разбирать JEP 401: value types. Начало в прошлом посте.
В JEP слово identity встречается 87 раз, практически в каждом абзаце. В этом посте разберу концепт идентичности и что меня в нём смущает.
В систему типов вводится новый термин - идентичность (identity):
▫️ У примитивов идентичности нет. 10 в одном месте ничем не отличается от 10 в другом месте
▫️ У ссылочных типов идентичность есть, каждый объект уникален. 2 объекта UserDTO - это разные объекты, даже если поля одинаковые
▫️ У value классов идентичности нет
С первого взгляда кажется, что идентичность это про == и equals. Но по мере чтения JEP растёт ощущение, что что-то не так. Например
1️⃣ Нет чёткого определения идентичности
Понятия типа identity относятся больше к проектированию модели данных. Value object, агрегаты и прочий DDD.
Java — это всё ещё язык программирования уровня циклы/классы. Примитивы и ссылочные типы описываются техническими характеристиками - стек, куча, наличие методов и тд.
Идейно Identity — что-то про возможность сравнения объектов между собой. Но что это означает с технической точки зрения в java — непонятно.
2️⃣ Сравнение value типов описано в JEP очень противоречиво:
💁🏼 С одной стороны, много раз повторили, что == сравнивает value объекты по значению полей. Как в примитивах, 123 == 123
🙋🏼 Но для некоторых value объектов equals вернет true, а == вернет false. Поэтому жирным шрифтом рекомендуют использовать equals
value класс - это всё ещё класс. Маловероятно, что кто-то будет использовать == для сравнения объектов. И объяснение какое-то есть. Но осадочек, что тема мутная, всё равно остаётся.
🌚 Конспирологическая теория, зачем на самом деле нужна identity
Внятного определения идентичности нет, значит понятие само по себе не важно. Гораздо важнее, что такое "тип без индентичности". Здесь написано чётко. Тип без идентичности
▫️ Immutable: неизменяемый
▫️ Interchangeable: взаимозаменяемый. Необязательно различать две сущности, если у них одинаковые значения
Для сущностей с этими свойствами добавляется пачка оптимизаций. Помимо уплощения кучи в списках есть, например, такая:
При создании value объекта через new, JVM может не создавать новый объект, а найти уже такой же существующий. == для таких объектов будет возвращать true.
Подобная идея лежит в основе пулов для базовых типов. При вызове Integer.valueOf(127) не создаётся новый объект, а берётся уже существующий из пула. Для классов-обёрток это существенная экономия.
Текущая цель модификатора value и концепта identity (вернее его отсутствия) - обозначить, к каким объектам можно применить пачку оптимизаций.
Новый тип и рассуждение про доменные сущности выглядит пока натянуто. Нужно явно обозначить определения, границы и ожидаемые эффекты от value. Типы данных - база, одна из первых тем при освоении языка. Должно быть простое объяснение, чем отличаются типы, без погружения в дебри возможных оптимизаций. Должно быть конкретное определение identity, если оно важно для выбора типа.
Годы работы и преподавания не проходят бесследно. Если что-то может быть понятно неверно - оно будет понято неверно💯 Поэтому пока оцениваю новую фичу на троечку
И ответ на вопрос перед постом. Сейчас в консоли будет false, объекты сравниваются по ссылке. Возможно, после введения value types тот же код вернёт true, и популярный вопрос с собеседований получит вторую жизнь. Но может и нет💅
👍62🔥31❤21👎4
Лучшие посты и новости
Ребята, не теряйте. У меня сейчас в жизни хайлоад, и до постов руки совсем не доходят. Но как станет полегче - продолжим исследовать чудный мир бэкенд разработки💅
Топ-5 постов этого года
▫️ База по Postgres: почему коннекшены это дорого, и что делает буфер и WAL
▫️ Почему однопоточный Redis быстрее многопоточного ConcurrentHashMap
▫️ CRaC — новая фича с большим потенциалом
▫️ Как прогреть кэши в Spring Boot
▫️ Как устроена многопоточность в разных языках
Мой фаворит - пост Почему в Set.of нельзя добавить дубликаты Маленькое расследование и теперь моя любимая байка про джаву
Новости для пользователей hard skills бота
1. Если ваша подписка попадает на январские праздники, вам добавляются две недели подписки. Хотите отдохнуть - отдыхайте, оплаченные дни никуда не пропадут.
(да, если подписаться сейчас, тоже будет 1.5 месяца по цене 1)
2. Появилась кнопка с итогами года, зацените!
Традиционное спасибо
Любимые подписчики, спасибо, что читаете. Вы все умнички! Что бы ни принёс следующий год, мы со всем справимся. Счастья нам всем в новом году💖
Ребята, не теряйте. У меня сейчас в жизни хайлоад, и до постов руки совсем не доходят. Но как станет полегче - продолжим исследовать чудный мир бэкенд разработки💅
Топ-5 постов этого года
▫️ База по Postgres: почему коннекшены это дорого, и что делает буфер и WAL
▫️ Почему однопоточный Redis быстрее многопоточного ConcurrentHashMap
▫️ CRaC — новая фича с большим потенциалом
▫️ Как прогреть кэши в Spring Boot
▫️ Как устроена многопоточность в разных языках
Мой фаворит - пост Почему в Set.of нельзя добавить дубликаты Маленькое расследование и теперь моя любимая байка про джаву
Новости для пользователей hard skills бота
1. Если ваша подписка попадает на январские праздники, вам добавляются две недели подписки. Хотите отдохнуть - отдыхайте, оплаченные дни никуда не пропадут.
(да, если подписаться сейчас, тоже будет 1.5 месяца по цене 1)
2. Появилась кнопка с итогами года, зацените!
Традиционное спасибо
Любимые подписчики, спасибо, что читаете. Вы все умнички! Что бы ни принёс следующий год, мы со всем справимся. Счастья нам всем в новом году💖
❤115🎄67🔥36
Какие у вас отношения с чистой архитектурой (Clean architecture)?
Anonymous Poll
10%
Видел на практике, работало отлично
28%
Видел на практике, но реализация была далека от "чистой"
29%
Знаю теорию, на практике не встречал
34%
Слышал, но пока не вникал
❤4👍2
Чистая архитектура: главы 1-2
Начала читать книгу Clean architecture Роберта Мартина.
Давно чувствую в этой теме какой-то пробел. Идея о непорочной бизнес-логике мне нравится, но на практике я не видела хороших примеров.
Видела сервисы, которые задумывались как "чистые", но в итоге становились проблемными. Протекающие слои, костыли, проблемы с транзакциями и атомарностью в целом. Не всегда понятно, как вписать некоторые задачи в рамку чистой архитектуры.
У меня нет больших ожиданий. Книга 2017 года, да и Мартин вряд ли плотно работал в энтерпрайзе, когда её писал. Но мне интересно почитать первоисточник и идейную составляющую.
И по первым впечатлениям - книга очень противоречивая.
Поднимается много тем - от парадигм программирования до структуры папок в проекте. Есть что обсудить👌
Но книгу читать сложно. Бесконечно много исторических справок и отступлений. Не всегда понятно, что хочет сказать автор, и как это связано с чистой архитектурой.
Поэтому решила делиться с вами процессом чтения. Это не будет подробный конспект, скорее спидран/реакция/саммари. Буду отмечать, что мне показалось интересным. Думаю, получится полезно.
Главы 1 и 2 - это введение. Большой смысловой нагрузки пока нет.
⭐️ Глава 1 ⭐️
Чем лучше архитектура, тем меньше усилий требуется на новые фичи и поддержку. Поддерживать плохую архитектуру со временем становится долго и дорого. При этом разработчики грустят, и менеджеры проваливают сроки
⭐️ Глава 2 ⭐️
Бизнес всегда требует работать над функционалом. Следить за архитектурой и состоянием системы, отстаивать необходимость рефакторинга и времени на проектирование - обязанность инженера.
Собственно, и всё.
Согласна с основной идеей второй главы, но читать крайне не рекомендую. В тексте очень много снобизма. Красной нитью идет мысль, что большинство разработчиков и менеджеров - узколобые макаки, которым плевать на архитектуру и лишь бы закрыть задачки поскорее.
В целом, Мартин кажется противоречивым персонажем, поэтому интересно, что будет дальше. Идеалистические концепты? Конкретные инструкции? 400 страниц воды? Посмотрим, обсудим
Начала читать книгу Clean architecture Роберта Мартина.
Давно чувствую в этой теме какой-то пробел. Идея о непорочной бизнес-логике мне нравится, но на практике я не видела хороших примеров.
Видела сервисы, которые задумывались как "чистые", но в итоге становились проблемными. Протекающие слои, костыли, проблемы с транзакциями и атомарностью в целом. Не всегда понятно, как вписать некоторые задачи в рамку чистой архитектуры.
У меня нет больших ожиданий. Книга 2017 года, да и Мартин вряд ли плотно работал в энтерпрайзе, когда её писал. Но мне интересно почитать первоисточник и идейную составляющую.
И по первым впечатлениям - книга очень противоречивая.
Поднимается много тем - от парадигм программирования до структуры папок в проекте. Есть что обсудить👌
Но книгу читать сложно. Бесконечно много исторических справок и отступлений. Не всегда понятно, что хочет сказать автор, и как это связано с чистой архитектурой.
Поэтому решила делиться с вами процессом чтения. Это не будет подробный конспект, скорее спидран/реакция/саммари. Буду отмечать, что мне показалось интересным. Думаю, получится полезно.
Главы 1 и 2 - это введение. Большой смысловой нагрузки пока нет.
⭐️ Глава 1 ⭐️
Чем лучше архитектура, тем меньше усилий требуется на новые фичи и поддержку. Поддерживать плохую архитектуру со временем становится долго и дорого. При этом разработчики грустят, и менеджеры проваливают сроки
⭐️ Глава 2 ⭐️
Бизнес всегда требует работать над функционалом. Следить за архитектурой и состоянием системы, отстаивать необходимость рефакторинга и времени на проектирование - обязанность инженера.
Собственно, и всё.
Согласна с основной идеей второй главы, но читать крайне не рекомендую. В тексте очень много снобизма. Красной нитью идет мысль, что большинство разработчиков и менеджеров - узколобые макаки, которым плевать на архитектуру и лишь бы закрыть задачки поскорее.
В целом, Мартин кажется противоречивым персонажем, поэтому интересно, что будет дальше. Идеалистические концепты? Конкретные инструкции? 400 страниц воды? Посмотрим, обсудим
🔥114👍47❤22🎄4👎3
Чистая архитектура. Главы 3-5
Продолжаем спидран по Clean Architecture Роберта Мартина.
⭐️ Глава 3. Парадигмы программирования ⭐️
определяют способы написания и организации кода. Их 3:
▫️ Структурная - ход программы описывается явно через условия и циклы. Используется для описания алгоритмов
▫️ Обьектно-ориентированная - программа описывается как взаимодействие объектов. Используется полиморфизм, ход работы не всегда очевиден
▫️ Функциональная - базируется на неизменяемости
⭐️ Глава 4. Структурное программирование ⭐️
Код делится на небольшие функции, которые можно протестировать. Но если тесты проходят - не факт, что программа работает корректно. Большую часть главы занимают истории, как Дейкстра боролся против goto и пытался математически доказать корректность программ.
⭐️ Глава 5. Обьектно-ориентированное (ОО) программирование ⭐️
Роберт рассуждает, что полезного и нового предлагает ООП
▫️ Инкапсуляция - не уникальна для ООП, плюс в современных языках не решает свою задачу.
Исходная цель инкапсуляции - скрыть данные и дать пользователю ограниченный набор методов.
В С (предшественник С++) определение функции и реализация разнесены по разным файлам, там инкапсуляция нормальная. В современных ОО языках инкапсуляция слабая. Нет разделения на определение и реализацию. Чтобы посмотреть, что умеет класс, надо зайти в класс, и тут открывается вся его внутрянка. Детали реализации как на ладони.
(мысль интересная, но с текущими IDE это не актуально. Выпадающие списки методов делают большой вклад в сокрытие сложности)
▫️ Наследование - всего лишь переопределение переменных/функций в ограниченном контексте. Добавочной ценности для архитектуры нет.
▫️ Полиморфизм легко и безопасно реализован в ОО языках, и в этом его главная ценность. Благодаря полиморфизму расцвел концепт инверсии зависимостей.
Дальше автор объясняет dependency inversion. Тут меня знатно побомбило.
DI - базовый кирпичик и первый шаг к чистой архитектуре. Это самая важная часть раздела. И она объяснена очень плохо:
▪️ Роберт вводит 2 типа стрелочек: source code dependency и flow of control. Не объясняя, что это такое
▪️ Рисует схему, где стрелочки идут вместе
▪️ Добавляет в схему ещё один квадрат (интерфейс) и поворачивает одну стрелку в другую сторону
На этом всё. Ни одного примера с кодом, только квадратики и стрелки. Этого недостаточно, чтобы каждый читатель понял, что нужно делать и в чём инверсия. Просто добавить интерфейс к каждому классу? Service и ServiceImpl это и есть тот самый power move?
Особенно дико такое объяснение выглядит на контрасте с менее важными темами. Рядом есть детальный разбор
✔️ разницы инкапсуляции в С и С++
✔️ как в С (язык без классов) можно имитировать наследование
✔️ как работают атомики в Clojure
Всё это разжевано гораздо подробнее, чем DI🤦
В 11 главе Роберт сделает второй заход в dependency inversion, но (спойлер) всё останется на уровне "просто добавь интерфейс". Это рекомендации по форме, но не по содержанию.
Интерфейсом может быть не только дополнительный квадратик, но и набор публичных методов класса. Чем больше этот набор скрывает детали реализации и упрощает работу с классом, тем лучше. И чтобы уменьшить связность, не обязательно множить сущности.
Продолжаем спидран по Clean Architecture Роберта Мартина.
⭐️ Глава 3. Парадигмы программирования ⭐️
определяют способы написания и организации кода. Их 3:
▫️ Структурная - ход программы описывается явно через условия и циклы. Используется для описания алгоритмов
▫️ Обьектно-ориентированная - программа описывается как взаимодействие объектов. Используется полиморфизм, ход работы не всегда очевиден
▫️ Функциональная - базируется на неизменяемости
⭐️ Глава 4. Структурное программирование ⭐️
Код делится на небольшие функции, которые можно протестировать. Но если тесты проходят - не факт, что программа работает корректно. Большую часть главы занимают истории, как Дейкстра боролся против goto и пытался математически доказать корректность программ.
⭐️ Глава 5. Обьектно-ориентированное (ОО) программирование ⭐️
Роберт рассуждает, что полезного и нового предлагает ООП
▫️ Инкапсуляция - не уникальна для ООП, плюс в современных языках не решает свою задачу.
Исходная цель инкапсуляции - скрыть данные и дать пользователю ограниченный набор методов.
В С (предшественник С++) определение функции и реализация разнесены по разным файлам, там инкапсуляция нормальная. В современных ОО языках инкапсуляция слабая. Нет разделения на определение и реализацию. Чтобы посмотреть, что умеет класс, надо зайти в класс, и тут открывается вся его внутрянка. Детали реализации как на ладони.
(мысль интересная, но с текущими IDE это не актуально. Выпадающие списки методов делают большой вклад в сокрытие сложности)
▫️ Наследование - всего лишь переопределение переменных/функций в ограниченном контексте. Добавочной ценности для архитектуры нет.
▫️ Полиморфизм легко и безопасно реализован в ОО языках, и в этом его главная ценность. Благодаря полиморфизму расцвел концепт инверсии зависимостей.
Дальше автор объясняет dependency inversion. Тут меня знатно побомбило.
DI - базовый кирпичик и первый шаг к чистой архитектуре. Это самая важная часть раздела. И она объяснена очень плохо:
▪️ Роберт вводит 2 типа стрелочек: source code dependency и flow of control. Не объясняя, что это такое
▪️ Рисует схему, где стрелочки идут вместе
▪️ Добавляет в схему ещё один квадрат (интерфейс) и поворачивает одну стрелку в другую сторону
На этом всё. Ни одного примера с кодом, только квадратики и стрелки. Этого недостаточно, чтобы каждый читатель понял, что нужно делать и в чём инверсия. Просто добавить интерфейс к каждому классу? Service и ServiceImpl это и есть тот самый power move?
Особенно дико такое объяснение выглядит на контрасте с менее важными темами. Рядом есть детальный разбор
✔️ разницы инкапсуляции в С и С++
✔️ как в С (язык без классов) можно имитировать наследование
✔️ как работают атомики в Clojure
Всё это разжевано гораздо подробнее, чем DI🤦
В 11 главе Роберт сделает второй заход в dependency inversion, но (спойлер) всё останется на уровне "просто добавь интерфейс". Это рекомендации по форме, но не по содержанию.
Интерфейсом может быть не только дополнительный квадратик, но и набор публичных методов класса. Чем больше этот набор скрывает детали реализации и упрощает работу с классом, тем лучше. И чтобы уменьшить связность, не обязательно множить сущности.
👍63❤25🔥17👎5
Тестовый контекст поднимается 2 минуты. У нас 4 класса с интеграционными тестами, их конфигурация ниже. Настройки прогона тестов стандартные. Сколько времени займет выполнение этих тестов?
@SpringBootTest
public class UserIT{...}
@ActiveProfiles("test")
@SpringBootTest
public class BidProcessingIT{...}
@SpringBootTest(properties = {
"user.rebalance.enabled=true"
})
public class RebalanceAccountIT{...}
@ActiveProfiles("test")
@SpringBootTest
public class AccountProcessingIT{...}
❤5
Сколько времени займет общий прогон тестов выше?
Anonymous Poll
16%
Чуть больше 2 минут
10%
Чуть больше 4 минут
17%
Чуть больше 6 минут
20%
Чуть больше 8 минут
15%
(количество методов с аннотацией @Test)*2
22%
👀
Как переиспользовать контекст в интеграционных тестах
Сегодня расскажу базовый минимум для написания интеграционных тестов в Spring: как работать с контекстом, чтобы тесты шли не по 2 часа.
В спринге интеграционным тестом называется тот, для которого нужен контекст. Контекст - это набор бинов, свойств, профилей и тд. Поднимать его для каждого теста долго, поэтому Spring по возможности переиспользует созданные ранее контексты.
Если в коде теста какой-то бин помечен
К новому контексту приводит переопределение свойств, указание профиля, конфигурация MockMvc и тд
Ответ на вопрос перед постом - прогон тестов будет занимать чуть больше 6 минут, потому что формируется 3 контекста. Для тестов BidProcessingIT и AccountProcessingIT используется один контекст.
🤔 Как оптимизировать выполнение тестов?
Популярный прием, который помогает зафиксировать контекст - базовый класс с тестовой конфигурацией. В базовом классе настраивается тестовый конфиг, тестконтейнеры, решается вопрос с секьюрити, наполнением и очисткой БД и тд
Остальные классы наследуются от базового:
При такой схеме важно следить, чтобы в наследниках не заменялись бины, свойства и тд. Иначе для них будет создан новый контекст.
На практике редко используется один базовый класс, чаще это иерархия под разные случаи. При грамотном проектировании и сознательности разработчиков интеграционные тесты выполняются за адекватное время даже для больших проектов👌
Сегодня расскажу базовый минимум для написания интеграционных тестов в Spring: как работать с контекстом, чтобы тесты шли не по 2 часа.
В спринге интеграционным тестом называется тот, для которого нужен контекст. Контекст - это набор бинов, свойств, профилей и тд. Поднимать его для каждого теста долго, поэтому Spring по возможности переиспользует созданные ранее контексты.
Если в коде теста какой-то бин помечен
@MockitoBean, @MockitoSpyBean или @TestBean, Spring создает для него прокси. Получается уникальный контекст, который нужно поднимать отдельно и нельзя в дальнейшем переиспользовать.К новому контексту приводит переопределение свойств, указание профиля, конфигурация MockMvc и тд
Ответ на вопрос перед постом - прогон тестов будет занимать чуть больше 6 минут, потому что формируется 3 контекста. Для тестов BidProcessingIT и AccountProcessingIT используется один контекст.
🤔 Как оптимизировать выполнение тестов?
Популярный прием, который помогает зафиксировать контекст - базовый класс с тестовой конфигурацией. В базовом классе настраивается тестовый конфиг, тестконтейнеры, решается вопрос с секьюрити, наполнением и очисткой БД и тд
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(…)
@TestExecutionListeners(…)
@ActiveProfile("test")
// куча других аннотаций
public abstract class BaseIntegrationTest {
// тестконтейнеры
// заглушки
}
Остальные классы наследуются от базового:
class UserIT extends BaseIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
public void shouldCreateUser() {…}
}При такой схеме важно следить, чтобы в наследниках не заменялись бины, свойства и тд. Иначе для них будет создан новый контекст.
На практике редко используется один базовый класс, чаще это иерархия под разные случаи. При грамотном проектировании и сознательности разработчиков интеграционные тесты выполняются за адекватное время даже для больших проектов👌
❤57👍35🔥21👎2