25698
Все самое полезное для Java-разработчика в одном канале. Список наших каналов: https://t.me/proglibrary/9197 Обратная связь: @proglibrary_feedback_bot По рекламе: @proglib_adv Прайс: @proglib_advertising
🐸 Библиотека джависта
#DevLife
❓ Как правильно определять границы сервисов в микросервисной архитектуре?
Один из недооценённых скиллов в разработке — это умение провести правильную границу между сервисами. Большинство «распределённых монолитов», которые мы встречаем в продакшене, рождаются именно здесь.
Разобрали тему — держи шпаргалку 👇
🔹 Что такое Service Boundary
Это контракт на три вещи:
— за что сервис отвечает;
— какими данными он владеет;
— как он общается с соседями.
Представь компанию: HR не лезет в финансы, а склад не занимается зарплатами. Так же должны работать твои сервисы.
🔹 4 принципа, которые работают
1. Business Capability First
Режь по бизнес-функциям, а не по техническим слоям. OrderService, PaymentService, UserService, потому что именно так бизнес думает о системе.
2. Single Responsibility
Один сервис — одна ответственность. Если в описании сервиса есть союз и, это уже тревожный звоночек 🚨
3. Data Ownership
У каждого сервиса своя БД. Без исключений. Shared database = shared pain.
4. Loose Coupling
Только API, никакого прямого доступа к чужим таблицам.
🔹 Классический антипаттерн
// ❌ Бизнес-логика смешана в одном сервисе
@Service
public class BadOrderService {
public String processOrderAndPayment(int id) {
String order = orderRepository.findById(id)
.orElse("Order not found");
String paymentStatus = "Payment Successful"; // 🚨 чужая ответственность
return order + " | " + paymentStatus;
}
}
⌛ Сохраняйте шпаргалку по HTTP кодам
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#Enterprise
🦾 Почему ваши AI-продукты на базе LLM ломаются (и как это чинить)?
Выкатили ИИ-фичу в прод, а она галлюцинирует, падает или выдает мусор? Приглашаем на открытый вебинар, где разберем реальную боль внедрения LLM-агентов и научимся делать так, чтобы «всё работало».
🗓️ Когда: 7 мая в 19:00 МСК
⏱️ Формат: 60 минут мяса + 30 минут ответов на ваши вопросы
🧑🏻💻 Кто вещает: Эмиль Сатаев — Backend Platform Developer (8+ лет в разработке). Человек, который своими руками внедряет LLM и агентные системы в реальные коммерческие сервисы.
🎁 Главный бонус для онлайна:
Только участникам прямого эфира подарим уникальный промокод на скидку 10.000 ₽ на большой курс AgentOps.
👉 Занять место на вебинаре
📦 Java медленно превращается в Haskell
Records, Sealed Classes, Pattern Matching — это уже не просто фичи. Это Data-Oriented Programming: иммутабельные данные отдельно, логика отдельно.
Для одних это долгожданная эволюция. Для других тихое убийство ООП, которому в Java 30 лет.
💬 Используешь DOP в продакшне? И что в целом думаешь об изменениях в языке?
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#DevLife
🕯 TimSort в Java
Arrays.sort(Object[]) и Collections.sort() используют TimSort — алгоритм, разработанный Тимом Петерсом для Python в 2002 году и портированный в Java 7. Для примитивов используется dual-pivot quicksort, но для объектов — только TimSort, и причина этого выбора нетривиальна.
🔹 Почему не quicksort для объектов
Quicksort не стабилен — одинаковые элементы могут поменяться местами. Для примитивов это безразлично: два одинаковых int неразличимы. Для объектов же критично: Comparator может считать два объекта равными по одному критерию, но они остаются разными объектами. Нестабильная сортировка ломает составные компараторы и пользовательские ожидания.
Merge sort стабилен, но требует O(n) дополнительной памяти и не использует частичную упорядоченность входных данных.
🔹 Как работает TimSort
TimSort строится на наблюдении: реальные данные редко полностью случайны. Массивы часто содержат уже упорядоченные подпоследовательности — runs.
Алгоритм:
1. Проходит по массиву, находя естественные runs — уже отсортированные (возрастающие или убывающие) подпоследовательности. Убывающие разворачиваются на месте.
2. Если run короче минимального порога (minRun, вычисляется от размера массива, обычно 32-64 элемента) — расширяется insertion sort'ом. Insertion sort эффективен на почти отсортированных коротких последовательностях.
3. Runs помещаются в стек и сливаются попарно через merge sort. Merge происходит не сразу — алгоритм поддерживает инвариант на стеке runs для гарантии O(n log n) в худшем случае.
🔹 Galloping mode
При слиянии двух runs TimSort переключается в режим gallopping если обнаруживает что элементы из одного run идут длинными сериями подряд. Вместо поэлементного сравнения — бинарный поиск: ищет где именно заканчивается серия из первого run. Это даёт O(n) на уже отсортированных данных вместо O(n log n).
Порог переключения в gallopping — MIN_GALLOP = 7. Если 7 раз подряд «выиграл» один run — переключаемся. Порог динамически адаптируется: если gallopping оказывается неэффективным, порог растёт.
🔹 Сложность
→ Лучший случай (уже отсортированный массив): O(n) — один run, ничего сливать не нужно.
→ Средний и худший: O(n log n).
→ Память: O(n) в худшем случае, но на практике меньше — временный буфер для merge не всегда равен половине массива.
🔹 Практическое следствие
Если данные поступают уже частично отсортированными — TimSort это эксплуатирует автоматически. Повторная сортировка почти отсортированного массива значительно дешевле первичной сортировки случайных данных. Это важно для пайплайнов где данные сортируются инкрементально или приходят из источника с естественным порядком.
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#CoreJava
🗓 Выброси Date и забудь Calendar
Если ты работал с java.util.Date или Calendar, то сталкивался с этими проблемами. Мутабельные объекты, месяцы с нуля, запутанный API. В Java 8 появился java.time (JSR-310), и это то, что должно было быть с самого начала.
🔹 Instant — source of truth
Instant now = Instant.now(); // 2024-01-15T19:00:00Z
// Из Unix timestamp
Instant fromSeconds = Instant.ofEpochSecond(1705341600L);
// Получить timestamp
long epochMilli = now.toEpochMilli();
// Арифметика
Instant later = now.plusSeconds(3600); // +1 час
// Наносекундная точность (не то что Date с миллисекундами)
long nanos = now.getNano();
LocalDateTime dt = LocalDateTime.of(2024, 1, 15, 19, 0, 0);
LocalDateTime parsed = LocalDateTime.parse("2024-01-15T19:00:00");
// ✅ Юзер ввёл дату без TZ
// ✅ Бизнес-логика без мультирегиональности
// ❌ Хранение в БД — используй Instant
// ❌ API-ответы — неоднозначно без TZ
ZonedDateTime ny = ZonedDateTime.now(ZoneId.of("America/New_York"));
ZonedDateTime tokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
// Конвертация между TZ — один и тот же момент времени
ZonedDateTime london = ny.withZoneSameInstant(ZoneId.of("Europe/London"));// 10 марта 2:30 AM не существует (переход на летнее время)
LocalDateTime nonExistent = LocalDateTime.of(2024, 3, 10, 2, 30);
ZonedDateTime adjusted = nonExistent.atZone(ZoneId.of("America/New_York"));
// → автоматически станет 3:30 AM EDT
// ZonedDateTime — знает правила DST, для будущих дат
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("America/New_York"));
// OffsetDateTime — просто смещение, без DST. Для прошлых событий и API
OffsetDateTime odt = OffsetDateTime.now(ZoneOffset.ofHours(-5));
LocalDateTime dt = LocalDateTime.of(2024, 1, 15, 19, 0, 0);
// Predefined
dt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); // "2024-01-15T19:00:00"
dt.format(DateTimeFormatter.BASIC_ISO_DATE); // "20240115"
// Custom
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
dt.format(fmt); // "2024-01-15 19:00:00"
// Парсинг с обработкой ошибок
public static Optional<LocalDateTime> parse(String s) {
try {
return Optional.of(LocalDateTime.parse(s, fmt));
} catch (DateTimeParseException e) {
return Optional.empty();
}
}
yyyy → 2024 MM → 01 dd → 15
HH → 19 (24h) hh → 07 (12h) mm → 00
ss → 00 SSS → 123 a → PM
z → EST Z → -0500 VV → America/New_York
// Duration — точный временной интервал
Duration duration = Duration.ofHours(24); // ровно 86400 секунд
// Period — календарный интервал
Period period = Period.ofDays(1); // 1 день (может быть 23 или 25 ч. при DST!)
// Оба работают с LocalDateTime, но семантика разная:
dt.plus(Duration.ofHours(24)); // +86400 сек
dt.plus(Period.ofDays(1)); // +1 календарный день
// Date → Instant
Instant instant = oldDate.toInstant();
// Instant → Date
Date date = Date.from(instant);
// Date → LocalDateTime
LocalDateTime ldt = oldDate.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
😮 Топ-вакансий для джавистов за неделю
Junior+ Java backend developer — удалёнка
Java-разработчик — от 290 000 ₽ —гибрид (Москва)
Senior Java Backend Developer — 200 000 – 400 000 ₽ — удалёнка/Гибрид (Ставрополь)
➡️ Еще больше топовых вакансий — в нашем канале Java jobs
🗓️ Уже через пару часов стартует вебинар!
Тема:
Как эффективно управлять контекстным окном LLM в мультиагентных системах и не сливать бюджет на токены
📋 Паттерн Command (Команда)
Command — поведенческий паттерн, который превращает запрос в самостоятельный объект. Этот объект содержит всю информацию о запросе: что делать, с какими параметрами и как это отменить.
Использование
🔹 Когда нужно откладывать выполнение операции или ставить её в очередь.
🔹 Когда требуется undo/redo — история операций.
🔹 Когда нужен аудит: кто, что и когда изменил.
🔹 Когда операции должны быть идемпотентными и безопасными для повтора при сбое.
Преимущества
1️⃣ Undo/Redo из коробки
Каждая команда знает, как себя отменить. Стек команд — готовая история изменений. Так работают все текстовые редакторы и графические инструменты.
2️⃣ Очереди и отложенное выполнение
Команды сериализуются и отправляются в Kafka или RabbitMQ. Воркеры их обрабатывают независимо. Если команда идемпотентна — безопасно повторить при сбое.
3️⃣ Полный audit log
Сохраняете команды в БД — получаете историю всех действий с параметрами. Это основа Event Sourcing: состояние системы восстанавливается воспроизведением команд.
4️⃣ Декомпозиция сложных операций
Макрокоманда объединяет несколько команд. Транзакционное выполнение, откат при ошибке — всё строится поверх простых команд.
🤖 Осталось 4 места на курс по ИИ-агентам. Набор закрывается 30 апреля.
🔗 Успеть на обучение
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#CoreJava
🐸 Библиотека джависта
#DevLife
🔥 База по ИИ-агентам от научного сотрудника Сколтеха и НИУ ВШЭ
Знакомьтесь, Екатерина Трофимова. Кандидат компьютерных наук, ресерчер в Центре ИИ Сколтеха и лаборатории LAMBDA. Она объединяет глубокую академическую экспертизу и практику: знает, как ИИ-системы устроены «под капотом» и как встроить их в реальные проекты (в т.ч. для Т-банка).
Мы попросили Екатерину собрать список мастхев материалов для тех, кто хочет проектировать агентов в проде. Сохраняйте список.
🛠 Стек и фреймворки:
DSPy — алгоритмическая оптимизация промптов (вместо ручного подбора слов).
Semantic Kernel и LangMem — инструменты для управления сессионной и долгосрочной памятью.
MCP (Model Context Protocol) — новый стандарт от Anthropic для подключения агентов к вашим БД и локальным файлам.
📖 Документация, которую нужно знать:
Anthropic Prompt Caching — как кэшировать контекст и радикально резать косты на API.
OpenAI Agents SDK / Cookbook — лучшие практики работы с памятью.
Augment — платформа для оптимизации работы ИИ-агентов и контроля токенов.
🔬 Хардкорные статьи и препринты (на выходные):
Lost in the Middle — почему LLM «слепнут» на длинных текстах и забывают середину контекста.
How Do Coding Agents Spend Your Money? — куда улетает бюджет при работе автономных кодинг-агентов.
MemGPT — архитектура операционной системы для LLM с иллюзией бесконечной памяти.
InjecAgent / AgentSentry — всё о безопасности и защите агентов от инъекций в промпты.
Екатерина Трофимова — один из ключевых экспертов нашего курса AgentOps. На своих лекциях она детально разбирает, как проектировать инструменты для агентов, как агент принимает решения о вызове инструментов и какие ограничения возникают в реальном проде
🎁 Акция в честь старта продаж!
Прямо сейчас при покупке Инженерного трека вы получаете полный доступ к материалам курса «Разработка ИИ-агентов» в подарок.
👉 Забрать 2 курса по цене 1 и начать обучение
🐸 Библиотека джависта
#DevLife
😮 Топ-вакансий для джавистов за неделю
Java-разработчик (GitFlic, гибрид) — от 200 000 ₽ — гибрид (Москва)
Java разработчик — 330 000 ₽ — гибрид/Удалёнка (Москве, Санкт-Петербург, Екатеринбург)
Senior Java Engineer (JavaSE, algorithms, optimization) — от 5 000 $ — удалёнка
➡️ Еще больше топовых вакансий — в нашем канале Java jobs
🐸 Библиотека джависта
#DevLife
🎤 Ваши знания по ИИ-агентам + наша аудитория в 1 млн человек = профит
Мы в Proglib активно качаем тему ИИ-агентов. Если вы в теме, то у нас есть предложение 👇
Что с нас?
- Огромный охват: пропиарим ваши соцсети и продукты на 1 000 000+ айтишников.
- Личный бренд: станете узнаваемым экспертом в самой горячей нише 2026 года.
- Никакой рутины: наши редакторы сами упакуют ваши мысли в крутые посты.
Что с вас?
Любой экспертный контент по ИИ-агентам: кейсы из прода, шпаргалки, статьи, наработки по стеку (LangGraph, CrewAI, AutoGen и др.) или просто мысли по архитектуре.
👉 Стать экспертом и заявить о себе
😮 Топ-вакансий для джавистов за неделю
Junior Java Developer — удалёнка — Expert Soft
Java-разработчик — от 300 тыс. руб — Локация: РФ / часовой пояс МСК
Team Lead разработки (Java) — удалёнка — АЭРО
➡️ Еще больше топовых вакансий — в нашем канале Java jobs
🐸 Библиотека джависта
#DevLife
☕️ Магия jcmd
Запустите jcmd <pid> Thread.print, и вы получите полный thread dump живого JVM-процесса — без kill -3, без рестарта, без агентов.
🔹 Зачем это нужно
— Полный дамп всех потоков с трассировками прямо на живом проде;
— Работает с любым JDK-процессом: Spring Boot, Quarkus, Kafka Streams;
— Один бинарник закрывает heap dump, GC-статистику, профилирование и диагностику;
— Не требует JMX, агентов или открытых портов — только PID.
🔹 Как использовать
— Список всех JVM-процессов и их PID:
jcmd
jcmd <pid> help
jcmd <pid> Thread.print
jcmd <pid> GC.heap_dump /tmp/heap.hprof
jcmd <pid> GC.heap_info
jcmd <pid> GC.run
jcmd <pid> GC.class_histogram
jcmd <pid> GC.class_histogram — первое, что стоит запустить при подозрении на утечку памяти: сразу видно, каких объектов неожиданно много и сколько они весят.
✔️ Java-тест: кэш, который врёт
Метод возвращает устаревшие данные. Иногда и только на нескольких потоках. Воспроизвести локально — нереально 👇
📦 Задание — code review
Команда добавила простой in-memory кэш для тяжёлых вычислений. На одном потоке работает идеально. На проде с нагрузкой — иногда возвращает старый результат или null.
@Component
public class PricingCache {
private final Map<String, BigDecimal> cache = new HashMap<>();
private final PricingEngine pricingEngine;
public PricingCache(PricingEngine pricingEngine) {
this.pricingEngine = pricingEngine;
}
public BigDecimal getPrice(String productId) {
if (cache.containsKey(productId)) {
return cache.get(productId);
}
BigDecimal price = pricingEngine.calculate(productId);
cache.put(productId, price);
return price;
}
public void invalidate(String productId) {
cache.remove(productId);
}
public void invalidateAll() {
cache.clear();
}
}
🐸 Библиотека джависта
#DevLife
🔥 4 привычки кодеров
Вот сколько общаюсь с разработчиками, постоянно слышу убеждение, что есть какой-то правильный способ писать софт. Все ищут секретную архитектуру, вылизывают паттерны, чтобы хоба и тимлид заплакал от счастья от твоего идеального кода.
но, я собрал 4 привычки адептов «чистого кода», (которые обычно все практикуют) 🤡
• Бесконечный рефакторинг рабочего кода.
Кажется, что так ты делаешь продукт лучше. Итог: жестко падаешь в перфекционизм. Переписываешь функцию по три раза, а бизнес ждет релиз. Закрываешь вкладку и в голове абсолютная пустота, время потрачено, а новых фичей ноль.
• Упарывание в сложную архитектуру
Сеньоры на ютубе обещают золотые горы, если внедрить микросервисы куда угодно. Итог: получаешь красивый overengineering-проект для мамы и 0 запущенных продуктов в срок, пока конкуренты клепают MVP на коленке.
• Душные споры на ревью
Неплохо, но как итог: ты пишешь полотна текста и тратишь часы на поиск глупой придирки к стилю, потому что банально фокус сместился с реальной задачи на эго.
• Ручная микро-оптимизация
Классика для тех, кто любит алгоритмы из универа. Итог: убиваешь дни жизни и выжимаешь миллисекунды, хотя бизнесу нужен был просто грязный, но рабочий скрипт еще вчера.
Проблема в том, что ни один из этих путей не дает самого главного - скорости и проверки гипотез. Реальному рынку плевать на твой идеальный код за 3 дня. Бизнес предпочтет код от ИИ-агента за 5 минут, который уже завтра начнет приносить деньги.
Хочешь обкатанный на нас лично и 100х учениках метод, как перестать кодить руками и начать делегировать задачи автономным системам?
👉 Заходи сюда, но у нас осталось всего 4 места, набор идет до завтрашнего дня.
P. S. Если интересно еще что-нибудь почитать от меня, то заходите в «Азбуку Айтишника», там я рассказываю об айти-базе, также у меня там есть бесплатный гайд на 15 глав по ии-агентам
🧵 Как починить мертвый ThreadLocal в пуле потоков
Если положить RequestId / userId / traceId в ThreadLocal и передать задачу в ExecutorService в итоге значение пропадёт. InheritableThreadLocal помогает только при явном создании потока, но не при переиспользовании из пула.
Alibaba решили это ещё в 2013 году, а библиотека до сих пор активно поддерживается → TransmittableThreadLocal (TTL).
🔹 Подключается:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.5</version>
</dependency>
TransmittableThreadLocal<String> traceId = new TransmittableThreadLocal<>();
traceId.set("req-123");
ExecutorService pool = TtlExecutors.getTtlExecutorService(
Executors.newFixedThreadPool(4)
);
pool.submit(() -> {
// "req-123" — доступен здесь, в потоке из пула
System.out.println(traceId.get());
});
-javaagent:transmittable-thread-local.jar
🐸 Библиотека джависта
#DevLife
☕️ Магия JVM Flags
Запустите своё приложение с -XX:+HeapDumpOnOutOfMemoryError, и когда (не «если») приложение упадёт с OOM, у вас будет дамп памяти.
🔹 Зачем это нужно
— При падении с OutOfMemoryError JVM автоматически сохраняет снапшот всей кучи в .hprof-файл.
— Без него вы знаете лишь факт смерти процесса. С ним — кто именно убил.
— Особенно ценно в контейнерах и CI/CD: процесс умер, лог ротировался, а дамп остался.
— Работает с любым анализатором: Eclipse MAT, IntelliJ Profiler, VisualVM, YourKit.
🔹 Как использовать
— Базовый запуск (дамп рядом с процессом):
java -XX:+HeapDumpOnOutOfMemoryError -jar app.jar
java -XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/dumps/app.hprof \
-jar app.jar
-v /host/dumps:/var/dumps
jcmd <pid> GC.heap_dump /tmp/live.hprof
💬 Обратная связь
Коллеги, предлагаю актуализировать базу годных ресурсов по Java помимо нашего канала.
✔️ Присылайте в комменты:
— Блоги и статьи
— Telegram-каналы
— YouTube-каналы и подкасты
— Репозитории
— Доклады
❌ Что НЕ ищем
Курсы "java с нуля за месяц", базовые туториалы и переводы официальной документации.
💬 Закидывайте в комменты ваши любимые источники. Для работы, для учёбы, для повседневного чтения.
🐸 Библиотека джависта
#DevLife
🔧 @Async и ThreadLocal: ловушка, которую не видно в коде
При использовании @Async Spring создаёт новый поток из пула для выполнения метода. Если в основном потоке вы храните данные в ThreadLocal (например, контекст пользователя, tenant ID или trace-информацию) — в асинхронном методе они будут недоступны.
Классическая ловушка:
// Основной поток — кладём данные
UserContext.set(currentUser);
// Вызов @Async метода — данные уже не там
asyncService.processOrder(orderId);
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(runnable -> {
User user = UserContext.get(); // захватываем из текущего потока
return () -> {
try {
UserContext.set(user); // передаём в новый поток
runnable.run();
} finally {
UserContext.clear(); // обязательно чистим
}
};
});
executor.setCorePoolSize(4);
executor.setMaxPoolSize(10);
executor.initialize();
return executor;
}
@Async("asyncExecutor")
public CompletableFuture<Void> processOrder(Long orderId) {
// UserContext.get() теперь вернёт правильного пользователя
}
⚙️ Compressed OOPs
На 64-битной JVM каждая ссылка на объект — это 64-битный указатель. При большом количестве объектов это заметно увеличивает потребление памяти и снижает эффективность кеша: меньше ссылок помещается в cache line. Compressed OOPs (Ordinary Object Pointers) — механизм, который решает эту проблему, но с нетривиальными последствиями при масштабировании heap.
🔹 Механика
JVM выравнивает объекты по 8 байт. Значит, три младших бита любого указателя всегда равны нулю и их можно не хранить. Если сдвинуть 32-битное значение влево на 3 бита, получим 35 бит адресного пространства — 32GB. Именно поэтому compressed oops работают при heap до 32GB: 32-битная "сжатая" ссылка декодируется в реальный 64-битный адрес сдвигом.
реальный адрес = сжатая_ссылка << 3
java -XX:+PrintFlagsFinal -version | grep UseCompressedOops
-Xlog:gc+heap=info:Heap address: 0x00000006c0000000, size: 28672 MB, Compressed Oops mode: Non-zero based
🗓 В следующий вторник (28.04) в 19:00 встречаемся в онлайне.
Тема:
Как эффективно управлять контекстным окном LLM в мультиагентных системах и не сливать бюджет на токены
⌛ Сохраняйте шпаргалку по спрингу
Собрали шпаргалку по Spring для тех, кто только начинает.
Карта экосистемы, главные аннотации и стартеры, типичные ошибки новичков всего на двух страницах.
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#Enterprise