Java-совет: когда нужно наполнять значения в Map, например списки, удобно использовать computeIfAbsent().
Классический способ:
С использованием computeIfAbsent:
👉 Java Portal
Классический способ:
Map<String, List<String>> map = new HashMap<>();
if (!map.containsKey("key")) {
map.put("key", new ArrayList<>());
}
map.get("key").add("listValue");
С использованием computeIfAbsent:
map.computeIfAbsent("key", k -> new ArrayList<>())
.add("listValue");Please open Telegram to view this post
VIEW IN TELEGRAM
👍17
Твой Dockerfile не "простой". Он просто недописан.
Большинство девов за всю жизнь используют всего 5 инструкций Dockerfile:
FROM, COPY, RUN, EXPOSE, CMD, ну и иногда ENV, если уж прям заморочились.😱
Вот почему твоя команда Ops и пользователи молча тебя ненавидят.
Вот несколько инструкций, про которые ты, скорее всего, не знаешь:
- HEALTHCHECK: контейнер не становится здоровым только потому, что он запущен.
Нет, Kubernetes сам не поймет, что приложение внутри уже сдохло.
Скажи ему, как это проверять. Или дай Ops’ам удовольствие дебажить цикл перезапусков контейнеров в проде.
- SHELL:
По умолчанию это /bin/sh -c. Хочешь другое, используй SHELL.
Меньше экранирования, меньше проблем с кроссплатформой.
- STOPSIGNAL: не оставляй приложение подвисшим.
Задает сигнал, который Docker отправляет при остановке контейнера.
Используй тот сигнал, который твое приложение реально обрабатывает.
Больше никаких зомби-контейнеров и сломанной "корректной остановки".
- ONBUILD
Хочешь сделать базовые образы умнее, используй это.
Хочешь развалить чужой пайплайн, используй неправильно.
- ARG vs ENV: перестань путать "во время сборки образа" и “во время запуска контейнера”,
ARG: существует только во время сборки
ENV: остается в финальном образе
Если ты не понимаешь разницу, не удивляйся, когда приложение ведет себя по-разному на dev и в prod.
- LABEL: метаданные не опциональны.
Это не для красоты. Это нужно инструментам, CI и аудиторам. Потом сам себе спасибо скажешь.
Dockerfile это не шелл-скрипт.
Это продовый код.
Он описывает систему, которую ты поставляешь.
Ты же не выкатываешь наполовину сделанное API, так перестань выкатывать наполовину сделанные образы.
Научись писать Dockerfile, который не отстой.💩
👉 Java Portal
Большинство девов за всю жизнь используют всего 5 инструкций Dockerfile:
FROM, COPY, RUN, EXPOSE, CMD, ну и иногда ENV, если уж прям заморочились.
Вот почему твоя команда Ops и пользователи молча тебя ненавидят.
Вот несколько инструкций, про которые ты, скорее всего, не знаешь:
- HEALTHCHECK: контейнер не становится здоровым только потому, что он запущен.
Нет, Kubernetes сам не поймет, что приложение внутри уже сдохло.
Скажи ему, как это проверять. Или дай Ops’ам удовольствие дебажить цикл перезапусков контейнеров в проде.
- SHELL:
По умолчанию это /bin/sh -c. Хочешь другое, используй SHELL.
Меньше экранирования, меньше проблем с кроссплатформой.
- STOPSIGNAL: не оставляй приложение подвисшим.
Задает сигнал, который Docker отправляет при остановке контейнера.
Используй тот сигнал, который твое приложение реально обрабатывает.
Больше никаких зомби-контейнеров и сломанной "корректной остановки".
- ONBUILD
Хочешь сделать базовые образы умнее, используй это.
Хочешь развалить чужой пайплайн, используй неправильно.
- ARG vs ENV: перестань путать "во время сборки образа" и “во время запуска контейнера”,
ARG: существует только во время сборки
ENV: остается в финальном образе
Если ты не понимаешь разницу, не удивляйся, когда приложение ведет себя по-разному на dev и в prod.
- LABEL: метаданные не опциональны.
Это не для красоты. Это нужно инструментам, CI и аудиторам. Потом сам себе спасибо скажешь.
Dockerfile это не шелл-скрипт.
Это продовый код.
Он описывает систему, которую ты поставляешь.
Ты же не выкатываешь наполовину сделанное API, так перестань выкатывать наполовину сделанные образы.
Научись писать Dockerfile, который не отстой.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤2😁1
Java tip: используй Files.walk(), чтобы рекурсивно пройтись по файлам в каталоге.
👉 Java Portal
public static void main(String[] args) {
Path startPath = Paths.get("src");
try (Stream<Path> paths = Files.walk(startPath)) {
paths
.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".java"))
.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}Please open Telegram to view this post
VIEW IN TELEGRAM
👍14❤3
Больше десяти лет
Прямолинейный. Синхронный. Простой.
С выходом Spring Framework 6 и Spring Boot 3 пришла современная замена:
- RestClient
❓ Чем обычно характеризуется
- API на методах (getForObject, postForEntity и т.д.)
- Очень явные вызовы под каждую HTTP-операцию
- Мало “цепочек”: часто один вызов укладывается в одну строку
- Настройки раскиданы по разным местам (interceptors, error handlers, converters)
- Синтаксис исторически нарастал кусками годами
Типичный пример:
Работает, но когда запрос усложняется (headers, auth, ошибки, таймауты), читаемость начинает страдать.
❓ RestClient меняет подход к API
- Флюентный, декларативный синтаксис
- Четкое разделение между сборкой request и чтением response
- Лучше читается, когда есть headers, params, auth и т.п.
- Единый, консистентный API для всех HTTP-методов
- Централизованный builder для общей конфигурации
Типичный пример:
Это та же идея, что у RestTemplate, но API яснее, расширяемее и проще поддерживать.
❓ Зачем RestClient, если уже есть WebClient
WebClient реактивный и неблокирующий (под другую задачу)
RestClient работает как RestTemplate: обычный (не реактивный) и с ожиданием ответа, просто это его современная замена.
Многим нужен современный API, но без перехода на реактивный стек.
Spring не пытается всех загнать в WebFlux. Он просто обновил классический HTTP-клиент.
Нет, он не умер, просто его только поддерживают: багфиксы и совместимость, без новых фич.
То есть новых возможностей не будет. Только апдейты совместимости.
А развитие будет происходить в RestClient.
Причем RestClient можно создать на базе RestTemplate:
❓ Что использовать сегодня:
Используй RestTemplate, если:
- приложение легаси
- миграция слишком дорогая
- завязан на библиотеки, которые работают только с RestTemplate
Используй RestClient, если:
- ты на Boot 3 / Spring 6+
- хочешь современный и чистый API
- хочешь остаться в императивном стиле
- не хочешь или не можешь использовать WebFlux
RestTemplate это классика, почти любой Java-разработчик на Spring хотя бы раз с ним сталкивался.
Но RestClient задает точку старта для нового.
Это не про реактивность и не про переделку архитектуры.
Это просто современный HTTP-клиент, который делает код понятнее и проще в сопровождении.
Похоже на подход многих других модулей Spring, где builder упрощает создание объектов и использование классов.
И в мире, где сервисы постоянно дергают внешние API, хороший HTTP-клиент реально важен.
👉 Java Portal
RestTemplate был главным вариантом для HTTP-запросов в Spring.Прямолинейный. Синхронный. Простой.
Он больше не развивается: новых фич и улучшений не будет, плюс есть ограничения по ошибкам и расширяемости.
С выходом Spring Framework 6 и Spring Boot 3 пришла современная замена:
RestTemplate- API на методах (getForObject, postForEntity и т.д.)
- Очень явные вызовы под каждую HTTP-операцию
- Мало “цепочек”: часто один вызов укладывается в одну строку
- Настройки раскиданы по разным местам (interceptors, error handlers, converters)
- Синтаксис исторически нарастал кусками годами
Типичный пример:
RestTemplate rest = new RestTemplate();
ResponseEntity<User> response =
rest.exchange(url, HttpMethod.GET, request, User.class);
Работает, но когда запрос усложняется (headers, auth, ошибки, таймауты), читаемость начинает страдать.
- Флюентный, декларативный синтаксис
- Четкое разделение между сборкой request и чтением response
- Лучше читается, когда есть headers, params, auth и т.п.
- Единый, консистентный API для всех HTTP-методов
- Централизованный builder для общей конфигурации
Типичный пример:
User user = client.get()
.uri(url)
.header("Authorization", token)
.retrieve()
.body(User.class);
Это та же идея, что у RestTemplate, но API яснее, расширяемее и проще поддерживать.
WebClient реактивный и неблокирующий (под другую задачу)
RestClient работает как RestTemplate: обычный (не реактивный) и с ожиданием ответа, просто это его современная замена.
Многим нужен современный API, но без перехода на реактивный стек.
Spring не пытается всех загнать в WebFlux. Он просто обновил классический HTTP-клиент.
❓ RestTemplate “мертв”
Нет, он не умер, просто его только поддерживают: багфиксы и совместимость, без новых фич.
То есть новых возможностей не будет. Только апдейты совместимости.
А развитие будет происходить в RestClient.
Причем RestClient можно создать на базе RestTemplate:
RestTemplate oldRestTemplate;
var restClient = RestClient.create(oldRestTemplate);
Используй RestTemplate, если:
- приложение легаси
- миграция слишком дорогая
- завязан на библиотеки, которые работают только с RestTemplate
Используй RestClient, если:
- ты на Boot 3 / Spring 6+
- хочешь современный и чистый API
- хочешь остаться в императивном стиле
- не хочешь или не можешь использовать WebFlux
RestTemplate это классика, почти любой Java-разработчик на Spring хотя бы раз с ним сталкивался.
Но RestClient задает точку старта для нового.
Это не про реактивность и не про переделку архитектуры.
Это просто современный HTTP-клиент, который делает код понятнее и проще в сопровождении.
Похоже на подход многих других модулей Spring, где builder упрощает создание объектов и использование классов.
И в мире, где сервисы постоянно дергают внешние API, хороший HTTP-клиент реально важен.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍6😁1
Java tip : можно использовать List.of() и Set.of(), чтобы создавать неизменяемые (immutable) коллекции.
Это быстрый способ получить немодифицируемые списки и множества без Collections.unmodifiableList().
Пример:
❌
👉 Java Portal
Это быстрый способ получить немодифицируемые списки и множества без Collections.unmodifiableList().
Пример:
List<String> letters = List.of("A", "B", "C");letters.add("D"); // исключение во время выполненияPlease open Telegram to view this post
VIEW IN TELEGRAM
🤯7👍6👀3🔥1
Postgres 18 получил поддержку виртуальных вычисляемых колонок. Вычисляемые STORED-колонки в Postgres уже были несколько версий подряд.
Вычисляемые колонки позволяют:
• создавать колонку на основе других данных
• ссылаться на значения из других колонок
• заранее считать колляции или любые вычисления в базе, а не в приложении
Синтаксис GENERATED ALWAYS AS открывает выражение, а в конце указывается режим VIRTUAL или STORED.
Виртуальные вычисляемые колонки пересчитываются при каждом чтении, поэтому не подходят для тяжёлых вычислений. Для таких случаев лучше использовать STORED-колонку или даже expression index. Но они удобны, когда значение нужно редко и его логично вычислять на лету.
Пример:
👉 Java Portal
Вычисляемые колонки позволяют:
• создавать колонку на основе других данных
• ссылаться на значения из других колонок
• заранее считать колляции или любые вычисления в базе, а не в приложении
Синтаксис GENERATED ALWAYS AS открывает выражение, а в конце указывается режим VIRTUAL или STORED.
Виртуальные вычисляемые колонки пересчитываются при каждом чтении, поэтому не подходят для тяжёлых вычислений. Для таких случаев лучше использовать STORED-колонку или даже expression index. Но они удобны, когда значение нужно редко и его логично вычислять на лету.
Пример:
CREATE TABLE products (
id serial PRIMARY KEY,
price numeric,
tax_rate numeric DEFAULT 0.05,
total_price numeric GENERATED ALWAYS AS (price * (1 + tax_rate)) VIRTUAL
);
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍3
Совет по Java: используйте default-методы в интерфейсах для поддержки обратной совместимости (начиная с Java 8).
Допустим, у вас есть интерфейс Shape:
Типичная реализация выглядит так:
Теперь нужно добавить метод perimeter(). Если добавить обычный метод в интерфейс, все существующие реализации Shape сломаются. Вместо этого можно добавить default-метод.
Класс Circle автоматически получает реализацию default-метода. При необходимости его можно переопределить.
пример : https://gist.github.com/mcasari/b0ee1d94046793ba20f02538ef916f48
👉 Java Portal
Допустим, у вас есть интерфейс Shape:
interface Shape {
double area();
}Типичная реализация выглядит так:
class Circle implements Shape {
private double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}Теперь нужно добавить метод perimeter(). Если добавить обычный метод в интерфейс, все существующие реализации Shape сломаются. Вместо этого можно добавить default-метод.
interface Shape {
double area();
// Новый метод
default double perimeter() {
...
}
}Класс Circle автоматически получает реализацию default-метода. При необходимости его можно переопределить.
пример : https://gist.github.com/mcasari/b0ee1d94046793ba20f02538ef916f48
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤6
Нужно загрузить ресурсы в Spring? Вот несколько рекомендуемых подходов:
-»
-»
-»
У каждого варианта своя зона применения.
Разбираем, когда и что использовать
👉 Java Portal
-»
@Value + Resource для classpath, файловой системы и URL-»
ResourceLoader для путей, которые определяются во время выполнения-»
ResourcePatternResolver для загрузки по шаблонамУ каждого варианта своя зона применения.
Разбираем, когда и что использовать
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
GitHub - danvega/resources
Contribute to danvega/resources development by creating an account on GitHub.
❤5👍3
WebFlux часто вызывает путаницу, потому что его обычно объясняют со стороны инструмента, а не со стороны проблемы.
Поэтому начнем с базы.
Сначала: блокирующий vs неблокирующий код
-» Блокирующий код:
Поток встает и ждет, пока операция не завершится.
Пока ждет, он ничего больше делать не может.
-» Неблокирующий код:
Поток делегирует ожидание и остается свободным для других задач.
Когда результат готов, выполняется продолжение.
Это не быстрее.
Это просто более эффективное использование потоков.
Еще одно ключевое понятие перед тем как идти дальше:
Backpressure. Это способность системы регулировать поток данных, чтобы не перегружать потребителя.
Представь так:
потребитель может сказать продюсеру: «присылай только столько, сколько я успеваю обработать».
Теперь к WebFlux.
WebFlux позволяет строить полностью неблокирующие приложения end-to-end:
-»Неблокирующий сервер (Netty)
-»Неблокирующие API
-»Проброс backpressure без перегрузки потребителя
-»Реактивная модель (Mono, Flux)
Ключевое слово здесь end-to-end.
Если хотя бы одна часть блокирует, весь выигрыш размывается.
Самая частая ошибка при работе с WebFlux
Использовать WebFlux, а потом:
-»Дернуть блокирующую базу данных
-»Использовать SDK без реактивной поддержки
-»Делать .block(), потому что так проще
В итоге:
-»Все равно все становится блокирующим
-»Ты просто добавил сложности
-»Ничего не выиграл
WebFlux не превращает блокирующий код в неблокирующий.
А Virtual Threads разве это не меняют?
Меняют, и это важно.
С Virtual Threads:
-»Блокирующая модель снова становится жизнеспособной
-»Цена ожидания резко падает
-»Необходимость в WebFlux уменьшается
Но есть нюанс.
Virtual Threads не дают:
-»Backpressure
-»Контроль потока
-»Композицию стримов
-»Настоящий стриминг данных
Они меняют tradeoff, но не убирают саму проблему.
Когда WebFlux все еще имеет смысл
WebFlux оправдан, когда:
-»Нужен реальный backpressure
-»Ты работаешь с непрерывными стримами (SSE, WebSockets)
-»Есть fan-out из множества внешних вызовов
-»Нужно явно контролировать поток данных
По необходимости, а не потому что модно.
Когда не стоит использовать WebFlux
-»Классический CRUD
-»Сложная бизнес-логика
-»Команды без реактивного опыта
-»Проекты, где читаемость важнее конкурентности
-»Когда Virtual Threads решают задачу с меньшей когнитивной стоимостью
WebFlux это другая модель исполнения.
Если твоя проблема не в масштабной I/O-конкурентности,
WebFlux тебе не поможет.
А сегодня, с Virtual Threads, многим он уже просто не нужен.
👉 Java Portal
Поэтому начнем с базы.
Сначала: блокирующий vs неблокирующий код
-» Блокирующий код:
Поток встает и ждет, пока операция не завершится.
Пока ждет, он ничего больше делать не может.
-» Неблокирующий код:
Поток делегирует ожидание и остается свободным для других задач.
Когда результат готов, выполняется продолжение.
Это не быстрее.
Это просто более эффективное использование потоков.
Еще одно ключевое понятие перед тем как идти дальше:
Backpressure. Это способность системы регулировать поток данных, чтобы не перегружать потребителя.
Представь так:
потребитель может сказать продюсеру: «присылай только столько, сколько я успеваю обработать».
Теперь к WebFlux.
WebFlux позволяет строить полностью неблокирующие приложения end-to-end:
-»Неблокирующий сервер (Netty)
-»Неблокирующие API
-»Проброс backpressure без перегрузки потребителя
-»Реактивная модель (Mono, Flux)
Ключевое слово здесь end-to-end.
Если хотя бы одна часть блокирует, весь выигрыш размывается.
Самая частая ошибка при работе с WebFlux
Использовать WebFlux, а потом:
-»Дернуть блокирующую базу данных
-»Использовать SDK без реактивной поддержки
-»Делать .block(), потому что так проще
В итоге:
-»Все равно все становится блокирующим
-»Ты просто добавил сложности
-»Ничего не выиграл
WebFlux не превращает блокирующий код в неблокирующий.
А Virtual Threads разве это не меняют?
Меняют, и это важно.
С Virtual Threads:
-»Блокирующая модель снова становится жизнеспособной
-»Цена ожидания резко падает
-»Необходимость в WebFlux уменьшается
Но есть нюанс.
Virtual Threads не дают:
-»Backpressure
-»Контроль потока
-»Композицию стримов
-»Настоящий стриминг данных
Они меняют tradeoff, но не убирают саму проблему.
Когда WebFlux все еще имеет смысл
WebFlux оправдан, когда:
-»Нужен реальный backpressure
-»Ты работаешь с непрерывными стримами (SSE, WebSockets)
-»Есть fan-out из множества внешних вызовов
-»Нужно явно контролировать поток данных
По необходимости, а не потому что модно.
Когда не стоит использовать WebFlux
-»Классический CRUD
-»Сложная бизнес-логика
-»Команды без реактивного опыта
-»Проекты, где читаемость важнее конкурентности
-»Когда Virtual Threads решают задачу с меньшей когнитивной стоимостью
WebFlux это другая модель исполнения.
Если твоя проблема не в масштабной I/O-конкурентности,
WebFlux тебе не поможет.
А сегодня, с Virtual Threads, многим он уже просто не нужен.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍6
This media is not supported in your browser
VIEW IN TELEGRAM
Тема Islands теперь используется по умолчанию во всех IDE от JetBrains.
Более мягкий и сбалансированный внешний вид, рассчитанный на комфорт и концентрацию во время работы.
Посмотреть подробнее👉 https://jb.gg/discover-the-islands
👉 Java Portal
Более мягкий и сбалансированный внешний вид, рассчитанный на комфорт и концентрацию во время работы.
Посмотреть подробнее
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11
Реальная история:
Дизайнерское агентство разработало интерфейс приложения в сервисе Figma, но из-за того, что карты Visa и Mastercard оказались заблокированы, не смогли выгрузить проект.
Осложнялось всё тем, что они могли олатить сервис только с корпоративной карты.
Читпей организовали оплату в течение 10 минут. Ребята оплатили подписку, и выгрузили проект. Интерфейс приложения был спасён!
Что делает CheatPay:
🔘Помогает оплатить зарубежные сервисы российскими картами, в том числе Visa и Mastercard.
🔘Восстанавливают доступ к аккаунту
🔘Работают прозрачно с оплатой на ИП и всеми закрывающими документами
Например:
Потеряли доступ к Zoom, Airtable, Atlassian, Canva и другим программам для управления IT-проектами? Простаивают проекты в Adobe Photoshop, Premiere Pro и After Effects? Другие сервисы? Помогут.
Обращаться в https://xn--r1a.website/cheatpay_ru
Дизайнерское агентство разработало интерфейс приложения в сервисе Figma, но из-за того, что карты Visa и Mastercard оказались заблокированы, не смогли выгрузить проект.
Осложнялось всё тем, что они могли олатить сервис только с корпоративной карты.
Читпей организовали оплату в течение 10 минут. Ребята оплатили подписку, и выгрузили проект. Интерфейс приложения был спасён!
Что делает CheatPay:
🔘Помогает оплатить зарубежные сервисы российскими картами, в том числе Visa и Mastercard.
🔘Восстанавливают доступ к аккаунту
🔘Работают прозрачно с оплатой на ИП и всеми закрывающими документами
Например:
Потеряли доступ к Zoom, Airtable, Atlassian, Canva и другим программам для управления IT-проектами? Простаивают проекты в Adobe Photoshop, Premiere Pro и After Effects? Другие сервисы? Помогут.
Обращаться в https://xn--r1a.website/cheatpay_ru
🤣4❤1🔥1😁1
Случайные UUID убивают производительность базы данных.
Ты перешел с целочисленных ID (1, 2, 3…) на UUID (a1b2-3c4d-…) ради безопасности или распределенной генерации.
И вдруг записи в БД стали медленнее. Иногда намного.
Вот почему.
Фрагментация индексов.
Большинство индексов в БД это B-Tree (сбалансированные отсортированные деревья). Физическое расположение данных имеет значение.
1. Последовательные ID
Когда ты вставляешь последовательные числа (1, 2, 3), новые записи всегда попадают в самый правый лист индекса.
Записи предсказуемые и последовательные.
Максимальные cache hit’ы.
Страницы индекса остаются заполненными на 100%.
Это максимальная скорость, на которую способна твоя база.
2. Случайные UUIDv4
UUIDv4 равномерно случайные. Это значит, что каждая новая вставка может попасть в любое место дерева.
Из-за этого:
-» База постоянно подгружает случайные страницы с диска в память (random I/O).
-» Page split. Если целевая страница заполнена, БД вынуждена делить ее пополам, в итоге получаются две полупустые страницы.
-» Эффект швейцарского сыра. Индекс раздувается и заполняется дырками, зря тратя RAM и место на диске.
-» Когда размер индекса превышает объем доступной памяти, пропускная способность на запись может просесть на 20–90%.
3. UUIDv7
Перестань использовать UUIDv4 в качестве primary key. Используй UUIDv7 (стандартизирован в RFC 9562).
UUIDv7 содержит таймстемп в начале ID, поэтому он сортируемый.
В итоге ты получаешь лучшее из двух миров:
-» Распределенная генерация, без центрального счетчика.
-» Монотонные вставки. В B-Tree они ведут себя почти как последовательные числа, без фрагментации.
-» Безопасность. Нельзя просто угадать ID (атакующий не узнает, что пользователь 101 идет сразу после 100), хотя стоит учитывать, что время создания записи становится видно.
Итог простой: ты сохраняешь удобство UUID и избавляешься от перфоманс-потерь.
👉 Java Portal
Ты перешел с целочисленных ID (1, 2, 3…) на UUID (a1b2-3c4d-…) ради безопасности или распределенной генерации.
И вдруг записи в БД стали медленнее. Иногда намного.
Вот почему.
Фрагментация индексов.
Большинство индексов в БД это B-Tree (сбалансированные отсортированные деревья). Физическое расположение данных имеет значение.
1. Последовательные ID
Когда ты вставляешь последовательные числа (1, 2, 3), новые записи всегда попадают в самый правый лист индекса.
Записи предсказуемые и последовательные.
Максимальные cache hit’ы.
Страницы индекса остаются заполненными на 100%.
Это максимальная скорость, на которую способна твоя база.
2. Случайные UUIDv4
UUIDv4 равномерно случайные. Это значит, что каждая новая вставка может попасть в любое место дерева.
Из-за этого:
-» База постоянно подгружает случайные страницы с диска в память (random I/O).
-» Page split. Если целевая страница заполнена, БД вынуждена делить ее пополам, в итоге получаются две полупустые страницы.
-» Эффект швейцарского сыра. Индекс раздувается и заполняется дырками, зря тратя RAM и место на диске.
-» Когда размер индекса превышает объем доступной памяти, пропускная способность на запись может просесть на 20–90%.
3. UUIDv7
Перестань использовать UUIDv4 в качестве primary key. Используй UUIDv7 (стандартизирован в RFC 9562).
UUIDv7 содержит таймстемп в начале ID, поэтому он сортируемый.
В итоге ты получаешь лучшее из двух миров:
-» Распределенная генерация, без центрального счетчика.
-» Монотонные вставки. В B-Tree они ведут себя почти как последовательные числа, без фрагментации.
-» Безопасность. Нельзя просто угадать ID (атакующий не узнает, что пользователь 101 идет сразу после 100), хотя стоит учитывать, что время создания записи становится видно.
Итог простой: ты сохраняешь удобство UUID и избавляешься от перфоманс-потерь.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥15👍5
Совет по Spring Boot: используйте TaskScheduler для реализации лёгких динамических задач. Для более сложных сценариев лучше смотреть в сторону Quartz.
Инжектим и используем:
Инжектируемый бин — это
При необходимости конфигурацию можно кастомизировать:
👉 Java Portal
Инжектим и используем:
@Autowired
TaskScheduler scheduler;
scheduler.schedule(
() -> System.out.println("Hello!"),
new CronTrigger("0 */5 * * * *") // каждые 5 минут
);
Инжектируемый бин — это
ThreadPoolTaskScheduler, встроенная реализация сразу TaskScheduler и ScheduledExecutorServiceПри необходимости конфигурацию можно кастомизировать:
@Configuration
@EnableScheduling
public class SchedulerConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setThreadNamePrefix("my-scheduler-");
scheduler.initialize();
return scheduler;
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1
В JDK 26 появилась новая фича: HttpClient, который входит в Java SE ещё со времён JDK 11, теперь поддерживает HTTP/3 😱
Подробнее
👉 Java Portal
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build(); // создать экземпляр HttpClient с HTTP/3 в качестве предпочтительной версии
URI reqURI = new URI("https://www.google.com/");
HttpRequest req = HttpRequest.newBuilder()
.uri(reqURI)
.build(); // создать экземпляр запроса
final HttpResponse.BodyHandler<String> bodyHandler =
BodyHandlers.ofString(StandardCharsets.UTF_8);
HttpResponse<String> resp = client.send(req, bodyHandler);
// отправить запрос и получить ответ в виде строки
System.out.println(
"status code: " + resp.statusCode() +
" HTTP protocol version: " + resp.version()
); // вывести код ответа и используемую версию HTTP
Подробнее
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12
Сегодня в рубрике crazy Java: этот код работает (на Java 25):
Но если заменить t++ на t += 1, всё ломается.
Ошибка:
👉 Java Portal
<T extends Integer> void test(T t) {
t++;
// t += 1;
IO.println(t);
}
void main() {
test(10);
}Но если заменить t++ на t += 1, всё ломается.
Ошибка:
Main.java:3: error: incompatible types: int cannot be converted to T
Please open Telegram to view this post
VIEW IN TELEGRAM
2🤯14❤3👍2😁2🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
Spring Boot 4 теперь полностью безопасен по работе с null — большой плюс для Kotlin-разработчиков.
С поддержкой JSpecify в Kotlin 2.2 наконец-то можно избавиться от платформенных типов и получить корректную работу с null даже в обобщённых типах.
Подробнее и попробовать Spring с Kotlin☺️
👉 Java Portal
С поддержкой JSpecify в Kotlin 2.2 наконец-то можно избавиться от платформенных типов и получить корректную работу с null даже в обобщённых типах.
Подробнее и попробовать Spring с Kotlin
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Этот проект показывает систему отслеживания местоположения курьера в реальном времени, похожую на то, как это реализовано в Zomato или Swiggy, и построенную на Spring Boot и Apache Kafka.
👉 Java Portal
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
GitHub - syedsameerpasha/Apache-Kafka-with-Spring-Boot: This project demonstrates a real-time delivery location tracking system…
This project demonstrates a real-time delivery location tracking system similar to Zomato/Swiggy, built using Spring Boot and Apache Kafka. - syedsameerpasha/Apache-Kafka-with-Spring-Boot
🤣4🌚3❤2💊1
Когда всё живёт в одной базе данных, с транзакциями всё просто.
BEGIN → COMMIT → ROLLBACK.
Но в распределённых системах всё резко усложняется.
В чём реальная проблема ???
В распределённых системах нельзя сделать одну транзакцию на всё сразу по нескольким причинам:
- несколько сервисов
- несколько баз данных
- множество возможных отказов
Классического rollback больше не существует.
И тут появляется паттерн Saga.
Saga это последовательность локальных транзакций, которые координируются между собой.
Каждый шаг:
- выполняет своё изменение
- фиксируется
- и определяет, как себя компенсировать, если дальше что-то пойдёт не так
Глобальной транзакции нет.
Есть eventual consistency.
Простой пример » Создание заказа может включать:
1. Создать заказ
2. Зарезервировать товар
3. Провести оплату
Если на шаге 3 происходит сбой, глобального rollback нет.
Выполняются компенсирующие действия:
- освободить резерв
- пометить заказ как отменённый
Это и есть Saga.
Два способа реализации Saga -
Хореография:
- сервисы реагируют на события
- центрального координатора нет
- слабая связность
- сложнее проследить общий поток
Оркестрация:
- есть компонент-координатор
- поток шагов явно описан
- проще рассуждать о логике
- выше связность
Частая ошибка » Считать, что Saga — это замена ACID-транзакциям.
Это не так.
Saga меняет сам контракт:
- допускаются промежуточные состояния
- компенсации проектируются явно
- принимается факт, что система может упасть на середине процесса
Когда Saga действительно имеет смысл
- долгоживущие процессы
- несколько сервисов
- реальные побочные эффекты: платежи, доставки, резервы
- ситуации, где технического rollback не существует
Если всё находится в одной базе данных, Saga не нужна. Здесь легко уйти в оверинжиниринг.
Ключевая мысль:
Явная сложность лучше, чем спрятанная за предположениями, которые больше не работают.
В распределённых системах сбои — это не исключение, а часть нормального потока.
Паттерн Saga не избавляет от всех проблем,
но помогает избежать беспорядка.
А в продакшене это уже большая разница.☃️
👉 Java Portal
BEGIN → COMMIT → ROLLBACK.
Но в распределённых системах всё резко усложняется.
В чём реальная проблема ???
В распределённых системах нельзя сделать одну транзакцию на всё сразу по нескольким причинам:
- несколько сервисов
- несколько баз данных
- множество возможных отказов
Классического rollback больше не существует.
И тут появляется паттерн Saga.
Saga это последовательность локальных транзакций, которые координируются между собой.
Каждый шаг:
- выполняет своё изменение
- фиксируется
- и определяет, как себя компенсировать, если дальше что-то пойдёт не так
Глобальной транзакции нет.
Есть eventual consistency.
Простой пример » Создание заказа может включать:
1. Создать заказ
2. Зарезервировать товар
3. Провести оплату
Если на шаге 3 происходит сбой, глобального rollback нет.
Выполняются компенсирующие действия:
- освободить резерв
- пометить заказ как отменённый
Это и есть Saga.
Два способа реализации Saga -
Хореография:
- сервисы реагируют на события
- центрального координатора нет
- слабая связность
- сложнее проследить общий поток
Оркестрация:
- есть компонент-координатор
- поток шагов явно описан
- проще рассуждать о логике
- выше связность
Ни один подход не является универсально лучшим.
Всё зависит от системы и контекста.
Частая ошибка » Считать, что Saga — это замена ACID-транзакциям.
Это не так.
Saga меняет сам контракт:
- допускаются промежуточные состояния
- компенсации проектируются явно
- принимается факт, что система может упасть на середине процесса
Когда Saga действительно имеет смысл
- долгоживущие процессы
- несколько сервисов
- реальные побочные эффекты: платежи, доставки, резервы
- ситуации, где технического rollback не существует
Если всё находится в одной базе данных, Saga не нужна. Здесь легко уйти в оверинжиниринг.
Ключевая мысль:
Saga не убирает сложность.
Она делает её явной.
Явная сложность лучше, чем спрятанная за предположениями, которые больше не работают.
В распределённых системах сбои — это не исключение, а часть нормального потока.
Паттерн Saga не избавляет от всех проблем,
но помогает избежать беспорядка.
А в продакшене это уже большая разница.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤1
Java Tip: Начиная с Java 14 можно использовать record для создания компактных неизменяемых объектов, которые просто переносят данные.
» Они короче обычных POJO
» У них из коробки есть equals(), hashCode() и toString()
» По умолчанию они неизменяемые
Классический POJO:
Вместо этого можно создать record:
Смысл ровно тот же, но без бойлерплейта.
👉 Java Portal
» Они короче обычных POJO
» У них из коробки есть equals(), hashCode() и toString()
» По умолчанию они неизменяемые
Классический POJO:
public class Book {
private final String title;
private final int price;
public Book(String title, int price) {
this.title = title;
this.price = price;
}
// геттеры, toString, equals и hashCode
}Вместо этого можно создать record:
public record Book(String title, int price) {}Смысл ровно тот же, но без бойлерплейта.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍19
Когда ты уже не джун и даже не мидл, на собеседованиях по Java почти не задают вопросы в стиле «что такое HashMap». В ход идут сценарии из реальной жизни.
Разберём типичный кейс.
Твой сервис — оркестратор. Он дергает несколько downstream-сервисов, чтобы обработать запрос.
Один из них проблемный: медленный и периодически отвечает 503 Service Unavailable.
1. Как сделать сервис устойчивым к ненадёжному downstream
Первое, что здесь просится » Circuit Breaker.
Идея простая: если зависимый сервис начинает фейлиться, мы перестаём его дёргать, чтобы:
- не тратить ресурсы,
- не увеличивать латентность,
- не убивать весь сервис каскадными таймаутами.
Реализация с Resilience4j:
Подключаем зависимость и оборачиваем вызов проблемного сервиса в CircuitBreaker.
Ключевые состояния:
Всё нормально. Запросы проходят. Ошибки считаются.
Порог ошибок превышен. Все вызовы сразу фейлятся, downstream даже не вызывается.
Пробный режим. Ограниченное число запросов, чтобы проверить — ожил сервис или нет.
Пример:
Конфиг обычно задаётся через application.yml:
- процент ошибок
- sliding window
- waitDurationInOpenState
- permittedNumberOfCallsInHalfOpenState
2. Что делать, когда circuit открыт (fallback)
Тут нет универсального ответа » зависит от бизнеса.
Типовые варианты:
🔹 Кэш
Если данные не критичны к свежести:
- Redis
- локальный cache
- stale-данные лучше, чем 503
🔹 Дефолтный ответ
Если можно вернуть «безопасное» значение:
- пустой список
- available = false
- статус UNKNOWN
🔹 Очередь
Если запрос важен, но не срочный:
- кладём событие в Kafka / Rabbit
- обрабатываем асинхронно
- отвечаем клиенту 202 Accepted
На практике часто комбинируют:
кэш + деградация функциональности.
3. Глобальная обработка ошибок через
Чтобы не размазывать try/catch по всему коду, делаем централизованный обработчик.
Пример бизнес-исключения
Глобальный handler
Плюсы подхода:
- чистые контроллеры
- единый формат ошибок
- нормальная мапа бизнес-ошибок на HTTP-статусы
Итог
На таком вопросе проверяют не знание аннотаций, а мышление:
- понимаешь ли ты отказоустойчивость
- умеешь ли деградировать сервис
- разделяешь ли бизнес-ошибки и технические фейлы
Это уже разговор не про «Java», а про архитектуру продакшн-сервисов
👉 Java Portal
Разберём типичный кейс.
Твой сервис — оркестратор. Он дергает несколько downstream-сервисов, чтобы обработать запрос.
Один из них проблемный: медленный и периодически отвечает 503 Service Unavailable.
1. Как сделать сервис устойчивым к ненадёжному downstream
Первое, что здесь просится » Circuit Breaker.
Идея простая: если зависимый сервис начинает фейлиться, мы перестаём его дёргать, чтобы:
- не тратить ресурсы,
- не увеличивать латентность,
- не убивать весь сервис каскадными таймаутами.
Реализация с Resilience4j:
Подключаем зависимость и оборачиваем вызов проблемного сервиса в CircuitBreaker.
Ключевые состояния:
Всё нормально. Запросы проходят. Ошибки считаются.
Порог ошибок превышен. Все вызовы сразу фейлятся, downstream даже не вызывается.
Пробный режим. Ограниченное число запросов, чтобы проверить — ожил сервис или нет.
Пример:
@CircuitBreaker(name = "inventoryService", fallbackMethod = "inventoryFallback")
public InventoryResponse getInventory(String productId) {
return inventoryClient.getInventory(productId);
}
Конфиг обычно задаётся через application.yml:
- процент ошибок
- sliding window
- waitDurationInOpenState
- permittedNumberOfCallsInHalfOpenState
2. Что делать, когда circuit открыт (fallback)
Тут нет универсального ответа » зависит от бизнеса.
Типовые варианты:
Если данные не критичны к свежести:
- Redis
- локальный cache
- stale-данные лучше, чем 503
private InventoryResponse inventoryFallback(String productId, Throwable ex) {
return cacheService.getInventory(productId);
}Если можно вернуть «безопасное» значение:
- пустой список
- available = false
- статус UNKNOWN
Если запрос важен, но не срочный:
- кладём событие в Kafka / Rabbit
- обрабатываем асинхронно
- отвечаем клиенту 202 Accepted
На практике часто комбинируют:
кэш + деградация функциональности.
3. Глобальная обработка ошибок через
@RestControllerAdviceЧтобы не размазывать try/catch по всему коду, делаем централизованный обработчик.
Пример бизнес-исключения
public class ProductNotFoundException extends RuntimeException {
public ProductNotFoundException(String id) {
super("Product not found: " + id);
}
}Глобальный handler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ProductNotFoundException.class)
public ResponseEntity<ErrorResponse> handleProductNotFound(ProductNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
"PRODUCT_NOT_FOUND",
ex.getMessage()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
}
JSON-ответ
{
"code": "PRODUCT_NOT_FOUND",
"message": "Product not found: 123"
}
Плюсы подхода:
- чистые контроллеры
- единый формат ошибок
- нормальная мапа бизнес-ошибок на HTTP-статусы
Итог
На таком вопросе проверяют не знание аннотаций, а мышление:
- понимаешь ли ты отказоустойчивость
- умеешь ли деградировать сервис
- разделяешь ли бизнес-ошибки и технические фейлы
Это уже разговор не про «Java», а про архитектуру продакшн-сервисов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤4