🔥 Устал каждый раз городить велосипед для Telegram-ботов на Spring Boot?
Новый готовый Spring Boot Starter решает именно эту боль: минимальная конфигурация, понятный pipeline обработки апдейтов, маршрутизация, обработка ошибок и простая интеграция в Spring-экосистему — всё из коробки.
Архитектура разделяет приём апдейтов (Ingress), Delivery, Interceptor, Dispatcher и Router/Handler, а также даёт готовые хуки расширения и обработки нестандартных сценариев.
🔗 Подробнее в статье
🔹 Курс «Алгоритмы и структуры данных»
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib
🐸 Библиотека джависта
#CoreJava
Новый готовый Spring Boot Starter решает именно эту боль: минимальная конфигурация, понятный pipeline обработки апдейтов, маршрутизация, обработка ошибок и простая интеграция в Spring-экосистему — всё из коробки.
Архитектура разделяет приём апдейтов (Ingress), Delivery, Interceptor, Dispatcher и Router/Handler, а также даёт готовые хуки расширения и обработки нестандартных сценариев.
🔹 Курс «Алгоритмы и структуры данных»
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥3❤1
List.of() появился в Java 9 и произвёл революцию в создании коллекций. Но под капотом это не просто обёртка над ArrayList — это целая иерархия оптимизированных классов.
📦 Сигнатура и варианты
Java предоставляет 12 перегруженных методов:
// Специализированные версии для 0-10 элементов
static <E> List<E> of()
static <E> List<E> of(E e1)
static <E> List<E> of(E e1, E e2)
// ... до 10 параметров
static <E> List<E> of(E e1, E e2, ..., E e10)
// Varargs для 11+ элементов
static <E> List<E> of(E... elements)
Зачем столько перегрузок?
— Избежать overhead создания varargs массива
— Каждая перегрузка возвращает оптимизированный класс
— Специализация для частых случаев (1-2 элемента)
→ Разные классы для разного размера:
// 0 элементов — singleton
static <E> List<E> of() {
return ImmutableCollections.EMPTY_LIST; // Переиспользуется!
}
// 1 элемент — List12 (List of 1 or 2)
static <E> List<E> of(E e1) {
return new ImmutableCollections.List12<>(e1);
}
// 2 элемента — тот же List12
static <E> List<E> of(E e1, E e2) {
return new ImmutableCollections.List12<>(e1, e2);
}
// 3+ элементов — ListN
static <E> List<E> of(E e1, E e2, E e3) {
return ImmutableCollections.listFromTrustedArray(e1, e2, e3);
}
→ Структура List12 (для 1-2 элементов):
static final class List12<E> extends AbstractImmutableList<E> {
private final E e0; // Всегда присутствует
private final E e1; // null если один элемент
List12(E e0) {
this.e0 = Objects.requireNonNull(e0);
this.e1 = null;
}
List12(E e0, E e1) {
this.e0 = Objects.requireNonNull(e0);
this.e1 = Objects.requireNonNull(e1);
}
public int size() {
return e1 != null ? 2 : 1;
}
public E get(int index) {
if (index == 0) return e0;
else if (index == 1 && e1 != null) return e1;
throw new IndexOutOfBoundsException();
}
}→ Структура ListN (для 3+ элементов):
static final class ListN<E> extends AbstractImmutableList<E> {
private final E[] elements; // Хранит все элементы
ListN(E... elements) {
// Копирование + null-check каждого элемента
this.elements = elements.clone();
for (E e : this.elements) {
Objects.requireNonNull(e);
}
}
public int size() {
return elements.length;
}
public E get(int index) {
return elements[index];
}
}1. Null элементы запрещены.
2. Varargs создаёт массив.
3. Не Serializable.
4. SubList тоже immutable.
📊 Performance бенчмарк
→ Создание списка из 3 элементов:
List.of("A", "B", "C"): ~5 ns
Arrays.asList("A", "B", "C"): ~8 ns
new ArrayList<>(Arrays.asList(...)): ~25 ns
List.of() в 5 раз быстрее ArrayList!→ Memory footprint:
List.of(1, 2, 3): 48 байт
Arrays.asList(1, 2, 3): ~72 байт (backing array + wrapper)
new ArrayList<>(3 items): ~112 байт (default capacity 10)
List.of() в 2.3 раза компактнее!
🔗 Документация
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16❤4🔥1
👀 Внутреннее устройство PriorityQueue
PriorityQueue — это реализация интерфейса Queue на основе двоичной кучи. Элементы всегда упорядочены по приоритету, минимальный элемент всегда в голове.
📦 Базовая структура
PriorityQueue использует min-heap реализацию:
Главные особенности:
→ O(log n) для add и poll (вставка/удаление с перестройкой).
→ O(1) для peek (доступ к минимуму).
→ O(n) для remove(Object) и contains.
→ Не гарантирует полную сортировку, только min в голове.
→ Не потокобезопасна (для concurrent используйте PriorityBlockingQueue).
🔍 Как устроено хранение
Binary Heap — complete binary tree в массиве
Свойство min-heap:
Каждый родитель ≤ своих детей. Это НЕ полная сортировка — только гарантия минимума в корне.
⚡️ Операции добавления и удаления
➕ add(E element) / offer(E element) — добавление
1. Элемент добавляется в конец массива: queue[size++] = element.
2. Вызывается siftUp(size-1) для восстановления heap property:
— Элемент "всплывает" вверх, пока не станет больше родителя.
— Сравнения через Comparator или Comparable.
Алгоритм siftUp:
Сложность: O(log n) — высота дерева.
➖ poll() — удаление минимального элемента
1. Сохраняется минимум: E result = queue[0].
2. Последний элемент перемещается в корень: queue[0] = queue[--size].
3. Вызывается siftDown(0) для восстановления heap:
— Элемент "тонет" вниз, меняясь с меньшим ребёнком.
Алгоритм siftDown:
Сложность: O(log n).
🔎 peek() — доступ к минимуму
Сложность: O(1) — прямой доступ к корню.
🔍 contains(Object o) — проверка наличия
1. Линейный поиск по массиву queue[].
2. Сравнение через equals().
Сложность: O(n) — требуется обход массива.
➖ remove(Object o) — удаление элемента
1. Линейный поиск элемента: O(n).
2. Удаление найденного:
— Перемещение последнего элемента на место удаляемого.
— siftDown() или siftUp() для восстановления heap.
Сложность: O(n) поиск + O(log n) восстановление = O(n).
⚖️ Важные нюансы
1️⃣ Comparator vs Comparable
Элементы должны быть сравниваемыми. Без Comparator/Comparable → ClassCastException.
2️⃣ НЕ полностью отсортирована
Heap гарантирует только минимум в корне.
3️⃣ Iterator не гарантирует порядок
Для sorted iteration используйте poll() в цикле:
4️⃣ Null элементы НЕ допускаются
✅ Подходит
— Нужен доступ к минимуму/максимуму
— Динамическая сортировка
— Алгоритмы с приоритетами
— Top K проблема
🔗 Документация: JavaDoc (Java 17)
Ставьте 🔥, если хотите ещё разбор.
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#CoreJava
PriorityQueue — это реализация интерфейса Queue на основе двоичной кучи. Элементы всегда упорядочены по приоритету, минимальный элемент всегда в голове.
📦 Базовая структура
PriorityQueue использует min-heap реализацию:
public class PriorityQueue<E> {
transient Object[] queue; // массив-куча
private int size = 0;
private final Comparator<? super E> comparator;
private static final int DEFAULT_INITIAL_CAPACITY = 11;
}Главные особенности:
→ O(log n) для add и poll (вставка/удаление с перестройкой).
→ O(1) для peek (доступ к минимуму).
→ O(n) для remove(Object) и contains.
→ Не гарантирует полную сортировку, только min в голове.
→ Не потокобезопасна (для concurrent используйте PriorityBlockingQueue).
Binary Heap — complete binary tree в массиве
PriorityQueue с элементами: [2, 5, 8, 9, 12, 10, 15]
Куча (min-heap):
2
/ \
5 8
/ \ / \
9 12 10 15
Массив queue[]:
[2, 5, 8, 9, 12, 10, 15]
0 1 2 3 4 5 6
Индексация:
- Родитель узла i: (i - 1) / 2
- Левый ребёнок узла i: 2 * i + 1
- Правый ребёнок узла i: 2 * i + 2
Свойство min-heap:
Каждый родитель ≤ своих детей. Это НЕ полная сортировка — только гарантия минимума в корне.
➕ add(E element) / offer(E element) — добавление
1. Элемент добавляется в конец массива: queue[size++] = element.
2. Вызывается siftUp(size-1) для восстановления heap property:
— Элемент "всплывает" вверх, пока не станет больше родителя.
— Сравнения через Comparator или Comparable.
Алгоритм siftUp:
void siftUp(int k) {
E x = queue[k];
while (k > 0) {
int parent = (k - 1) >>> 1; // деление на 2
E e = queue[parent];
if (compare(x, e) >= 0) break;
queue[k] = e; // родитель опускается
k = parent; // поднимаемся выше
}
queue[k] = x; // вставляем элемент
}Сложность: O(log n) — высота дерева.
➖ poll() — удаление минимального элемента
1. Сохраняется минимум: E result = queue[0].
2. Последний элемент перемещается в корень: queue[0] = queue[--size].
3. Вызывается siftDown(0) для восстановления heap:
— Элемент "тонет" вниз, меняясь с меньшим ребёнком.
Алгоритм siftDown:
void siftDown(int k) {
E x = queue[k];
int half = size >>> 1; // size / 2
while (k < half) {
int child = (k << 1) + 1; // левый ребёнок
E c = queue[child];
int right = child + 1;
if (right < size && compare(c, queue[right]) > 0) {
child = right; // правый меньше
c = queue[child];
}
if (compare(x, c) <= 0) break;
queue[k] = c; // ребёнок поднимается
k = child; // опускаемся ниже
}
queue[k] = x;
}Сложность: O(log n).
🔎 peek() — доступ к минимуму
E peek() {
return (size == 0) ? null : (E) queue[0];
}Сложность: O(1) — прямой доступ к корню.
🔍 contains(Object o) — проверка наличия
1. Линейный поиск по массиву queue[].
2. Сравнение через equals().
Сложность: O(n) — требуется обход массива.
➖ remove(Object o) — удаление элемента
1. Линейный поиск элемента: O(n).
2. Удаление найденного:
— Перемещение последнего элемента на место удаляемого.
— siftDown() или siftUp() для восстановления heap.
Сложность: O(n) поиск + O(log n) восстановление = O(n).
⚖️ Важные нюансы
Элементы должны быть сравниваемыми. Без Comparator/Comparable → ClassCastException.
Heap гарантирует только минимум в корне.
Для sorted iteration используйте poll() в цикле:
✅ Подходит
— Нужен доступ к минимуму/максимуму
— Динамическая сортировка
— Алгоритмы с приоритетами
— Top K проблема
Ставьте 🔥, если хотите ещё разбор.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍3❤1
Часто выбор коллекции ограничивается только таблицей сложностей и на этом всё.
Но реальный кейс сложнее: средняя сложность ≠ реальная скорость в продакшне. JVM, кэш процессора, GC и паттерны доступа могут радикально поменять картину.
🔑 Главная мысль
Выбирайте коллекцию под сценарий использования, а не “по самой быстрой ячейке в таблице”.
ArrayList хранит элементы в массиве → локальность памяти + CPU кэш → итерации летят.
Вставка в середину за O(n), но при небольших списках разница с LinkedList исчезающе мала.
🔧 Паттерн использования:
— 90% чтение, редкие вставки → идеально.
— Если заранее известно примерное кол-во элементов → задайте initialCapacity, иначе ArrayList будет несколько раз пересоздавать массив (copy O(n) на каждом росте).
В бенчмарках JMH даже при вставке в середину ArrayList часто быстрее LinkedList просто потому, что LinkedList платит за “pointer chasing” (скачки по памяти, cache-miss).
Да, вставка/удаление в начало или конец за O(1).
Но get(i) = O(n), и каждый шаг = новый объект, новая ссылка → нагрузка на GC.
🔧 Паттерн использования:
— Когда нужна двусторонняя очередь с частыми удалениями/добавлениями в начало и конец.
— Во всех остальных случаях лучше ArrayDeque, он без лишних объектов и быстрее почти всегда.
LinkedList ест больше памяти: на каждый элемент два указателя + объект-узел.
HashMap даёт O(1) доступ при хорошем hashCode().
Но:
— Если хэши “плохие” → коллизии → O(log n)
— При достижении load factor 0.75 → resize → перераспределение всех бакетов (дорогая операция).
🔧 Паттерн использования:
— Когда нужен быстрый поиск по ключу без сохранения порядка или когда важно хранить уникальные элементы или строить словари/кэши по ключу.
— Если знаете примерное кол-во элементов → сразу задайте кол-во элементов в конструкторе new HashMap<>(N).
Начиная с Java 8 при коллизии, когда LinkedList становится длинным (по умолчанию ≥ 8 элементов) → список превращается в красно-чёрное дерево.
Дают O(log n) доступ и всегда хранят ключи отсортированными.
Но если сортировка нужна редко, дешевле собрать HashMap и вызвать sorted() на стриме.
🔧 Паттерн использования:
— Когда важно поддерживать сортировку на каждой операции (напр. Top-N задач в приоритетной очереди).
— Не храните mutable-ключи, т.к. можно “потерять” элемент при изменении поля, участвующего в compareTo.
TreeMap хранит узлы с балансировкой (красно-чёрное дерево) → накладные расходы на память + сравнения ключей.
LinkedHashMap поддерживает порядок вставки или порядок доступа (accessOrder=true).
Можно сделать LRU-кэш, переопределив removeEldestEntry.
🔧 Паттерн использования:
— Когда важен порядок, но сортировка не нужна.
— Когда нужно легко реализовать ограниченный кэш.
Каждый get() в режиме accessOrder вызывает перестановку в двусвязном списке → небольшие накладные расходы.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava #лучшее2025
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16❤3🔥2👏1
Механизм сборки мусора в JVM — это не просто “магия, которая чистит память”, а сложная система, работающая по поколениям, фазам и стратегиям.
Понимание его внутренней архитектуры важно, если вы хотите управлять производительностью, избегать утечек и эффективно настраивать параметры JVM.
🔹 Архитектура: как устроена куча (Heap)
Куча памяти делится на поколения:
Heap
├── Young Generation
│ ├── Eden Space
│ └── Survivor Spaces (S0, S1)
└── Old Generation (Tenured)
Eden — вновь созданные объекты.
Survivor — те, кто “выжил” после первой сборки.
Old Gen — объекты, пережившие несколько сборок, считаются “долгоживущими”.
Дополнительно есть Metaspace (с Java 8), где хранятся данные о классах.
🔹 Алгоритм работы GC: по фазам
1. Mark
GC начинает с “корневых” ссылок (стек, глобальные переменные) и помечает все достижимые объекты.
2. Sweep
Удаляются все немаркированные объекты — они считаются “мертвыми”.
3. Compact (в некоторых GC)
Уплотнение памяти: “живые” объекты перемещаются ближе друг к другу, чтобы избежать фрагментации.
🔹 Типы сборок
1. Minor GC
Запускается при заполнении Eden. Очищаются только молодые поколения. Быстро, но может происходить часто.
2. Major GC / Full GC
Включает Old Gen и Metaspace. Дорогая операция, может “заморозить” все потоки (stop-the-world pause).
🔹 Типы сборщиков и их принципы
— Serial GC: однопоточная сборка. Просто и медленно.
— Parallel GC: многопоточная сборка всех поколений. Высокая пропускная способность.
— G1 GC: делит кучу на регионы, параллельно собирает “Region Set”. Поддерживает предсказуемые паузы.
— ZGC: целиком конкурентный сборщик. Работает с огромными кучами (до терабайта), паузы <10 мс.
— Shenandoah: минимальные паузы за счёт почти полной конкуренции с пользовательскими потоками.
🔹 Как GC определяет, что объект мёртв?
GC не использует reference count. Он строит граф достижимости:
1. Начинает с “корней” (GC roots)
2. Если оттуда нельзя добраться до объекта — он считается мусором
3. Это позволяет избежать утечек при циклических ссылках
🔹 Советы по оптимизации
— Избегайте долгоживущих ссылок (static, ThreadLocal) без необходимости
— Используйте WeakReference, если хотите избежать удержания объекта GC
— Кэшируйте объекты осознанно — утечка через Map может быть незаметной
— Задавайте лимиты памяти (-Xms512m -Xmx1024m)
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava #лучшее2025
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥4❤2👏2
💼⌛️ ТОП-5 причин, почему программист не может долго найти работу
Почему некоторые разработчики остаются "между работами" месяцы?
Не всегда дело в нехватке вакансий или «рынок просел». Часто дело в подходе к поиску проекта. Вроде бы есть опыт, стек, даже pet-проекты, но офферов всё нет.
Часто корень проблемы — неумение продать себя правильно. Отказ выполнять тестовые задания, считая их ненужными или обидными. Кроме того, нежелание рассматривать стажировки как стартовую площадку для получения опыта и расширения профессиональных связей также может замедлить процесс трудоустройства. И это далеко не все возможные причины.
🔗 Подробнее в статье
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#CoreJava #лучшее2025
Почему некоторые разработчики остаются "между работами" месяцы?
Не всегда дело в нехватке вакансий или «рынок просел». Часто дело в подходе к поиску проекта. Вроде бы есть опыт, стек, даже pet-проекты, но офферов всё нет.
Часто корень проблемы — неумение продать себя правильно. Отказ выполнять тестовые задания, считая их ненужными или обидными. Кроме того, нежелание рассматривать стажировки как стартовую площадку для получения опыта и расширения профессиональных связей также может замедлить процесс трудоустройства. И это далеко не все возможные причины.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava #лучшее2025
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5😁3🔥2👏1
📈 Как «ленивая разработка» захватывает IT-рынок
Пока мы выстраиваем архитектуру, пишем тесты и спорим о лучших практиках, рынок всё активнее обживают те, кто вообще не пишет код. Low-code и no-code решения не просто живы — они становятся нормой для бизнеса.
Порог входа минимальный, скорость разработки — бешеная, а заказчику всё равно, написано ли это на Java или накликано в визуальном редакторе. Вопрос: как долго останется актуальной классическая разработка?
🔗 Подробнее в статье
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#CoreJava #лучшее2025
Пока мы выстраиваем архитектуру, пишем тесты и спорим о лучших практиках, рынок всё активнее обживают те, кто вообще не пишет код. Low-code и no-code решения не просто живы — они становятся нормой для бизнеса.
Порог входа минимальный, скорость разработки — бешеная, а заказчику всё равно, написано ли это на Java или накликано в визуальном редакторе. Вопрос: как долго останется актуальной классическая разработка?
🔗 Подробнее в статье
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava #лучшее2025
Please open Telegram to view this post
VIEW IN TELEGRAM
😁11🥱3👍1🔥1🤔1
⚡️ Параллельные стримы: ускорение или нет?
Java предоставляет мощный инструмент для обработки данных — параллельные стримы. Они позволяют автоматически распределять вычисления по нескольким потокам, но их эффективность зависит от множества факторов.
Добавление parallelStream() бездумно — это не "оптимизация", а лотерея с шансом на баги и падение.
❌ Когда не использовать
— При небольшом наборе данных (<10 000 элементов) затраты на управление потоками могут превышать прирост скорости.
— Операции sorted(), distinct() или limit() требуют полного знания данных, что снижает эффективность параллельного выполнения.
— Вложенные parallelStream() в CompletableFuture или ExecutorService могут привести к конкуренции за ресурсы и неожиданному падению производительности.
✔️ Когда использовать
— Обработка больших объёмов данных (100 000+ элементов).
— Операции независимы и ресурсоёмки, например, сложные вычисления, парсинг файлов, загрузка данных из сети.
🔍 Важная особенность
parallelStream() использует ForkJoinPool.commonPool(). Если есть другие задачи, использующие этот же пул, они могут начать конкурировать за потоки, замедляя всё приложение.
💬 Делитесь в комментах интересными кейсами
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#CoreJava #лучшее2025
Java предоставляет мощный инструмент для обработки данных — параллельные стримы. Они позволяют автоматически распределять вычисления по нескольким потокам, но их эффективность зависит от множества факторов.
Добавление parallelStream() бездумно — это не "оптимизация", а лотерея с шансом на баги и падение.
— При небольшом наборе данных (<10 000 элементов) затраты на управление потоками могут превышать прирост скорости.
— Операции sorted(), distinct() или limit() требуют полного знания данных, что снижает эффективность параллельного выполнения.
— Вложенные parallelStream() в CompletableFuture или ExecutorService могут привести к конкуренции за ресурсы и неожиданному падению производительности.
— Обработка больших объёмов данных (100 000+ элементов).
— Операции независимы и ресурсоёмки, например, сложные вычисления, парсинг файлов, загрузка данных из сети.
parallelStream() использует ForkJoinPool.commonPool(). Если есть другие задачи, использующие этот же пул, они могут начать конкурировать за потоки, замедляя всё приложение.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava #лучшее2025
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥2❤1👏1
Kafka транзакции не панацея, а узкоспециализированный инструмент.
🔹 Что гарантируют Kafka транзакции:
— Атомарную публикацию в несколько топиков (все сообщения видны или ни одно).
— Связывание read → process → write в одну операцию для консьюмеров-продюсеров.
🔹 Что НЕ гарантируют:
— Откат изменений во внешних системах (БД, API, email).
— Защиту от дубликатов, если downstream-сервисы не настроены правильно.
🔹 Два реальных сценария использования транзакций:
Отправка события обработки заказа и событие в audit-топик с временной меткой. Если сервис упадёт между отправками, то при повторе время не совпадёт — история будет некорректной.
Транзакция гарантирует, что оба сообщения либо станут видимы консьюмерам одновременно, либо ни одно из них не будет доставлено.
Если сервис упадёт после отправки сообщений, но до коммита оффсета, downstream-сервисы увидят дубликаты.
Транзакции связывают отправку и коммит в одну атомарную операцию.
Транзакции добавляют накладные расходы и подходят только для специфических кейсов.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤3🔥2👏1
Идемпотентность — это свойство операции, которую можно выполнить несколько раз, получая тот же результат, что и при однократном выполнении.
Простыми словами: повторный вызов не меняет состояние системы после первого успешного выполнения.
Классический пример — HTTP методы: GET, PUT, DELETE идемпотентны, POST — нет.
🔹 Ключевые моменты
▪️ Идемпотентная операция: setStatus(ACTIVE) — сколько раз ни вызови, статус будет ACTIVE.
▪️ Неидемпотентная операция: balance += 100 — каждый вызов увеличивает баланс.
▪️ Идемпотентность ≠ отсутствие побочных эффектов (это чистота функций).
▪️ Критична для распределённых систем: retry-механизмы, очереди сообщений, API.
▪️ Защищает от дублирующих запросов при сетевых сбоях.
🔹 Под капотом
В реальных системах идемпотентность достигается через:
→ Idempotency Key — клиент генерирует уникальный ключ и передаёт в запросе. Сервер проверяет: если операция с таким ключом уже выполнялась — возвращает закешированный результат.
→ Версионирование — оптимистичные блокировки через версии записей (JPA @Version).
→ Уникальные идентификаторы — вместо "создай заказ" отправляем "создай заказ с ID=xyz". Повторный запрос с тем же ID игнорируется.
→ Статус-машины — переходы между состояниями: если уже в целевом состоянии, ничего не делаем.
🔹 Подводные камни
— Idempotency key нужно хранить ограниченное время (обычно 24 часа).
— Нужна атомарность проверки и выполнения.
— Сложность при асинхронной обработке.
— Не все бизнес-операции можно сделать идемпотентными.
— Overhead на хранение и проверку ключей.
— REST API с критичными операциями (платежи, создание заказов).
— Kafka consumers — защита от повторной обработки при rebalance.
— Интеграция с внешними системами через retry.
— Scheduled jobs, которые могут запуститься дважды.
— Распределённые транзакции (Saga pattern).
— Webhook обработчики.
— Любые операции с денежными средствами.
— Внутренние CRUD операции без side effects.
— Операции чтения (они идемпотентны по умолчанию).
— Высоконагруженные операции, где overhead критичен.
— Простые внутренние методы без внешних вызовов.
🔧 Бонус-трюк: в Spring можно создать аннотацию @Idempotent и реализовать через AOP с использованием Redis для хранения idempotency keys. Получается декларативная идемпотентность на уровне методов.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12🔥4❤1👏1
🔥 Field vs Constructor Injection: зомби-объекты в production
Казалось бы, избитая тема. Но копните глубже @Autowired — и обнаружите, что ваши объекты после new существуют в состоянии "клинической смерти".
Автор разбирает не "удобство тестов" (это мелочи), а фундаментальные проблемы: нарушение контракта конструктора, проблемы с JMM, хрупкость при AOT-компиляции для GraalVM, race conditions в мультипоточке.
Рекомендую прочитать, если хотите понять почему Spring Framework 6 окончательно выбрал constructor injection, а не просто "так все делают".
🔗 Читайте подробнее
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#coreJava
Казалось бы, избитая тема. Но копните глубже @Autowired — и обнаружите, что ваши объекты после new существуют в состоянии "клинической смерти".
Автор разбирает не "удобство тестов" (это мелочи), а фундаментальные проблемы: нарушение контракта конструктора, проблемы с JMM, хрупкость при AOT-компиляции для GraalVM, race conditions в мультипоточке.
Рекомендую прочитать, если хотите понять почему Spring Framework 6 окончательно выбрал constructor injection, а не просто "так все делают".
══════ Навигация ══════
Вакансии • Задачи • Собесы
#coreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥5👏2