Backend Developer Java
Глубокое понимание Java и JVM для backend-разработки. Курс разбирает не синтаксис, а семантику языка и поведение платформы: коллекции и их стоимость, Stream API, устройство JVM — память, сборку мусора, JIT, — модель памяти и многопоточность, внутренности Spring (контейнер, прокси, транзакции), JPA/Hibernate, Kafka и production-практики: тестирование, наблюдаемость, контейнеры, устойчивость. Цель — научиться видеть стоимость абстракций и поведение кода под нагрузкой, а не просто заставлять его работать.
Что внутри курса
01
Язык Java
7 уроковСемантика языка Java: система типов и упаковка, строки и иммутабельность, контракт equals/hashCode, интерфейсы, обобщения и стирание типов, records и sealed-типы, модель исключений. Не синтаксис, а поведение и стоимость решений.
- Типы: примитивы, ссылки и упаковкаДва мира типов: значение против объекта. Автоупаковка и её цена в аллокациях, кэш обёрток -128..127 и почему == на Integer — ловушка, а не сравнение значений.
- Строки: пул, иммутабельность и конкатенацияИммутабельность String как источник разделения, потокобезопасности и кэша hashCode; пул литералов и ловушка == vs equals; почему конкатенация в цикле требует StringBuilder.
- Контракт equals и hashCodeequals и hashCode как единый контракт: пять свойств, правило «равные → равный хеш» и почему без него объект теряется в HashMap; симметрия при наследовании (getClass против instanceof).
- Интерфейсы: default-методы и функциональные интерфейсыdefault-методы для эволюции интерфейсов без слома реализаций и правила разрешения ромба; функциональные интерфейсы и граница с абстрактным классом по состоянию и множественности.
- Обобщения и стирание типовОбобщения как compile-time-механизм, стираемый к рантайму: откуда unchecked и запрет instanceof, почему List<String> инвариантно не связан с List<Object>, и wildcards по правилу PECS.
- Records, sealed-типы и сопоставление с образцомRecord как неизменяемый носитель данных с генерируемыми членами, а sealed + pattern matching — исчерпывающий switch, где компилятор ловит забытый вариант вместо рантайма.
- Модель исключенийИерархия Throwable как классификация ошибок, checked против unchecked как решение об обязанности вызывающего, try-with-resources с подавленными исключениями и chaining с cause против антипаттернов, ломающих диагностику.
02
Коллекции
6 уроковJava Collections Framework изнутри: устройство ArrayList и HashMap, стоимость операций, хеширование и коллизии, упорядоченные структуры, fail-fast итераторы и потокобезопасные коллекции. Выбор коллекции — это выбор асимптотики и памяти.
- Иерархия коллекций и стоимость операцийДве оси выбора: интерфейс задаёт контракт, реализация — цену. Иерархия List/Set/Queue и отдельная Map, профили асимптотики и выбор коллекции по горячим операциям.
- ArrayList и LinkedList: массив против связного спискаПочему ArrayList обгоняет LinkedList даже на вставках: амортизированный рост массива, локальность данных и кэш процессора — и где Big-O вводит в заблуждение.
- HashMap изнутриМассив бакетов, хеш ключа и поиск через equals: коллизии и треификация, load factor и рехеш, и почему O(1) держится только при хорошем hashCode и иммутабельных ключах.
- TreeMap, LinkedHashMap и упорядоченностьТри вида порядка: никакого (Hash), вставки/доступа (LinkedHash, основа LRU) и сортированный (Tree, O(log n)) — с ловушкой «сравнение по compareTo, а не equals».
- Итераторы и fail-fast поведениеfor-each как Iterator и механизм modCount: почему ConcurrentModification Exception — это однопоточный баг обхода, как удалять правильно и почему fail-fast не защищает от потоков.
- Потокобезопасные коллекцииТри уровня потокобезопасности и правило атомарности: ConcurrentHashMap с lock-free чтением и compound-методами, CopyOnWriteArrayList для read-heavy и блокирующие очереди для producer-consumer.
03
Stream API и функциональный стиль
5 уроковФункциональный слой Java: лямбды и ссылки на методы, конвейер Stream с ленивыми промежуточными операциями, коллекторы, Optional и цена параллельных стримов.
- Лямбды и функциональные интерфейсыЛямбда как реализация единственного метода функционального интерфейса с типом из target type; стандартный набор java.util.function, ссылки на методы, захват через effectively final и лексическая прозрачность this.
- Конвейер Stream: ленивость и исполнениеStream как ленивый конвейер: промежуточные операции описывают и ничего не считают до терминальной, элементы текут поэлементно (отсюда short-circuit и бесконечные источники), а сам стрим одноразовый.
- Коллекторы: сбор, группировка, агрегацияCollector как композируемый рецепт свёртки: toMap с требованием уникальных ключей (иначе IllegalStateException) или merge, groupingBy/partitioningBy и downstream-агрегация группы за один проход.
- Optional: семантика и границы примененияOptional как контракт в типе возвращаемого значения «результата может не быть»: извлечение через orElse/ifPresent вместо опасного get, композиция map/flatMap и почему он не для полей и параметров.
- Параллельные стримы и их ценаparallelStream как разделяй-и-властвуй на ОБЩЕМ commonPool: выгоден лишь при делимом источнике, большом N×Q и stateless-операциях, а блокирующие задачи и мелкие данные делают его вредным.
04
JVM: память и сборка мусора
7 уроковСреда выполнения Java: устройство памяти (куча, стеки, metaspace), поколения и алгоритмы сборки мусора, загрузка классов, JIT-компиляция и диагностика проблем с памятью. Отделяет гарантии языка от поведения под нагрузкой.
- Память JVM: куча, стеки, metaspaceКарта раздельных областей памяти с разными лимитами: объект в куче, ссылка в стеке, классы в metaspace — и почему каждый вид OutOfMemoryError лечится по-своему, а не -Xmx.
- Сборка мусора: достижимость и поколенияМусор — недостижимое от корней (циклы собираются, забытые ссылки — нет); гипотеза «объекты умирают молодыми» даёт поколения, дешёвый minor против дорогого full и паузы stop-the-world.
- Алгоритмы GC: G1, ZGC и выбор коллектораКоллекторы как точки на оси «пропускная способность против задержки»: сбалансированный дефолт G1, субмиллисекундный ZGC ценой накладных расходов, и выбор по профилю нагрузки.
- Ссылки: strong, soft, weak, phantomЧетыре силы ссылок как шкала политик достижимости: soft под давлением памяти против weak сразу, phantom для пост-мортем очистки — и почему finalize заменён try-with-resources и Cleaner.
- Загрузка классовИерархия загрузчиков с делегированием вверх и идентичность класса как «имя + загрузчик»: изоляция плагинов, «Foo к Foo не кастуется» и разница ClassNotFoundException против NoClassDefFoundError.
- JIT: от интерпретации к машинному кодуДвухфазное исполнение: интерпретатор ради старта, JIT горячего ради пика; tiered C1/C2, инлайнинг и escape analysis, деоптимизация — и почему бенчмарк без прогрева лжёт.
- OutOfMemoryError и поиск утечекДиагностика памяти в четыре шага: область из сообщения, утечка против недоразмера по live set, утечка как достижимое ненужное и поиск удерживателя через heap dump и dominator tree.
05
Многопоточность
7 уроковМногопоточность в Java: потоки и их жизненный цикл, мониторы и synchronized, volatile и видимость, пулы потоков и ExecutorService, CompletableFuture, явные блокировки и координация, виртуальные потоки. Гонки, взаимоблокировки и как их не создавать.
- Потоки: создание, состояния, прерываниеШесть состояний потока, демоны и завершение JVM, и почему остановить поток можно только кооперативно: interrupt как просьба и правильная реакция на InterruptedException.
- Мониторы: synchronized, wait и notifyМонитор объекта и взаимное исключение, координация через wait/notify с обязательным while-циклом, и почему разный порядок захвата двух мониторов даёт взаимоблокировку.
- Volatile, атомарность и гонки данныхДве ортогональные проблемы — видимость (чинит volatile) и атомарность (не чинит): почему count++ остаётся гонкой и как выбрать между volatile, Atomic и блокировкой.
- Пулы потоков и ExecutorServiceПул разделяет задачу и поток: неинтуитивная логика core→очередь→max→отказ, ловушка неограниченной очереди в newFixedThreadPool и отказ как backpressure.
- CompletableFuture: композиция асинхронных задачКонвейер стадий вместо блокирующего get: thenApply/thenCompose/thenCombine, ошибки, текущие по цепочке через exceptionally/handle, и осознанный выбор исполнителя вместо commonPool.
- Явные блокировки и примитивы координацииНабор специализированных инструментов под тип задачи: ReentrantLock с tryLock против deadlock, ReadWriteLock для read-heavy и Semaphore/Latch/ Barrier для не-взаимоисключающей координации.
- Виртуальные потокиСмена экономики блокирующего I/O: отмонтирование от carrier при блокировке, масштаб вместо скорости, и три границы — не для CPU, беречься pinning, не пулить.
06
Модель памяти Java
4 урокаФормальная модель того, что потоки видят в памяти: happens-before, безопасная публикация объектов, атомики и CAS, ThreadLocal. Объясняет, почему код без синхронизации может видеть устаревшие данные.
- Happens-before и гонки данныхВидимость между потоками возникает только через ребро happens-before (монитор, volatile, start, join, program order); два конфликтующих доступа без ребра — data race, а data-race-free программа ведёт себя sequentially consistent.
- Безопасная публикация и finalПубликация ссылки и инициализация полей не упорядочены без happens-before — объект утекает недостроенным; final даёт видимость через freeze при отсутствии this-escape, а идиомы публикации и DCL с volatile создают нужное ребро.
- Атомики и CAScount++ теряет обновления; CAS даёт атомарное условное обновление и lock-free оптимистичный цикл, Atomic его инкапсулируют с volatile-видимостью, под контеншеном LongAdder бьёт AtomicLong, а CAS уязвим к ABA.
- ThreadLocal: изоляция состояния по потокуThreadLocal даёт каждому потоку свою копию — делить нечего, синхронизация не нужна; идеален для per-request контекста, но в пуле поток переживает задачу, поэтому без remove() в finally значение протекает в следующий запрос.
07
Spring Core и Boot
5 уроковОснование Spring: IoC-контейнер и ApplicationContext, конфигурация и сканирование компонентов, автоконфигурация Spring Boot, externalized configuration с профилями и запуск приложения со встроенным сервером.
- IoC-контейнер и ApplicationContextЧто такое IoC-контейнер и бины: как ApplicationContext читает конфигурацию и строит граф объектов, чем он отличается от BeanFactory и почему синглтоны создаются на старте приложения.
- Конфигурация: @Configuration, @Bean и сканированиеДве дороги объявить бин — @Configuration/@Bean и сканирование со стереотипами. Почему вызов @Bean-метода не создаёт новый объект и чем full-режим конфигурации отличается от lite.
- Автоконфигурация и стартерыОткуда в Boot-приложении берутся бины, которых никто не писал: стартеры, условия @Conditional, уступка пользовательским бинам и чтение condition evaluation report.
- Внешняя конфигурация и профилиКак Boot разрешает конфликты конфигурации: стопка источников от файлов в jar до аргументов запуска, relaxed binding, @ConfigurationProperties и профили окружений.
- Старт приложения и встроенный серверЧто происходит между main() и приёмом трафика: ось событий старта, выбор типа контекста, встроенный Tomcat как бин, раннеры и диагностика падений и медленного старта.
08
Бины и внедрение зависимостей
5 уроковЖизненный цикл бина от определения до уничтожения, области видимости, способы внедрения зависимостей, разрешение неоднозначностей и циклические зависимости. Неверная область видимости — источник трудноуловимых багов.
- Жизненный цикл бинаКонвейер создания бина: инстанцирование, внедрение, колбэки инициализации в фиксированном порядке и пост-процессоры, на которых появляются прокси. Почему @PostConstruct — не «после старта приложения».
- Области видимости биновScope как правило создания экземпляров: чем Spring-синглтон отличается от GoF, почему prototype не уничтожается контейнером и как чинить инъекцию короткоживущего бина в долгоживущий.
- Способы внедрения зависимостейКонструктор, сеттер и поле как разные наборы гарантий: иммутабельность, non-null и тестируемость без контейнера. Правила @Autowired и цена полевой инъекции.
- Разрешение неоднозначностейЧто делает контейнер, когда кандидатов несколько: воронка выбора, @Primary и @Fallback как приоритет, @Qualifier как смысловой фильтр и коллекции как «взять всех».
- Циклические зависимостиПочему кольцо зависимостей неразрешимо для конструкторов, чем платит сеттерная лазейка с ранними ссылками, почему Boot запрещает циклы по умолчанию и как разрывать их дизайном.
09
AOP и прокси
4 урокаКак работает «магия» Spring: динамические прокси JDK и CGLIB, модель аспектов, @Transactional под капотом и ограничения прокси-подхода — самовызов, final-методы, видимость.
- Динамические прокси: JDK и CGLIBПрокси как отдельный объект перед целью: JDK-прокси по интерфейсам против CGLIB-подкласса, дефолт Spring Boot, ограничения final и private и почему вызов через this минует обёртку.
- Модель Spring AOP: аспекты, advice, pointcutСловарь AOP поверх знакомых прокси: аспект, пять типов совета как try/catch/finally, язык точек среза execution и @annotation, границы рантайм-плетения Spring.
- @Transactional под капотомТранзакционный перехватчик на прокси: почему checked-исключения не откатывают, что такое rollback-only маркер и UnexpectedRollbackException, и как выбирать между REQUIRED, REQUIRES_NEW и NESTED.
- Ограничения прокси: самовызов и границыДиагностика молчащих аннотаций: один вопрос «прошёл ли вызов через прокси» и пять причин отказа — от самовызова и final-методов до бинов, созданных слишком рано.
10
Spring MVC и REST API
5 уроковСлой, где контракт API превращается в код: путь запроса через DispatcherServlet, контроллеры и привязка параметров, валидация, единый формат ошибок и сериализация через Jackson.
- DispatcherServlet: путь запросаКарта пути HTTP-запроса: front controller с делегатами по ролям, execution chain с интерцепторами, запись REST-ответа в адаптере и честная разница между фильтром и интерцептором.
- Контроллеры и привязка параметровСигнатура как декларация каналов: путь, query, заголовки и тело через резолверы аргументов, правила обязательности и конверсии, три типовых 400 и ловушка DTO без @RequestBody.
- Валидация входных данныхBean Validation на границе API: включатели @Valid и метод-валидация с разными исключениями, фаза между привязкой и методом и честная граница «форма — на входе, истина — в домене».
- Обработка ошибок и ProblemDetailФормат ошибок как часть контракта: @ControllerAdvice как единая таблица перевода исключений, стандарт ProblemDetail с application/problem+json и готовая база для всех Spring-исключений.
- Сериализация: Jackson и DTOОдин настроенный JsonMapper на приложение, управление формой JSON и три болезни сериализации сущностей — почему наружу выходят только намеренные контракты-DTO.
11
HTTP и REST-семантика
4 урокаСемантика протокола, на которой держится любой API: методы и коды состояния, безопасность и идемпотентность, кэширование с условными запросами, версионирование и эволюция контракта.
- Методы и коды состоянияМетод как объявленное намерение и код как честный машиночитаемый исход: семантика методов по RFC 9110, граница 4xx/5xx и почему «200 на всё» ломает кэши, ретраи и алерты.
- Безопасность и идемпотентностьДва контрактных свойства методов по RFC 9110: почему идемпотентность — про эффект на состояние, а не про ответ, и как ключ идемпотентности делает POST безопасным для повтора.
- Кэширование и условные запросыДва рычага HTTP-кэша — свежесть и ревалидация через ETag/304 — и вторая роль ETag: защита от потерянных обновлений через If-Match/412, та же оптимистическая блокировка, что @Version.
- Версионирование и эволюция APIВерсию порождает только ломающее изменение: классификация изменений, стратегии версионирования с компромиссами и управляемая депрекация через заголовки Deprecation и Sunset.
12
JPA и Hibernate
7 уроковORM поверх реляционной БД: persistence context и жизненный цикл сущности, маппинг связей, стратегии загрузки и проблема N+1, запросы и проекции, транзакции и блокировки, кэши Hibernate. Ключевой навык — видеть, какой SQL порождает код.
- ORM и persistence contextРабочая память между кодом и базой: identity map, dirty checking и write-behind. Почему UPDATE происходит без save, а SQL выполняется не в порядке кода.
- Жизненный цикл сущностиЧетыре состояния сущности как отношение к контексту: почему merge возвращает другую managed-копию, что на самом деле делает save Spring Data и как определить состояние объекта в любой точке кода.
- Маппинг связейОдин внешний ключ против двух объектных ссылок: владелец связи и mappedBy, хелперы синхронизации, осознанные каскады и отличие orphanRemoval от cascade=REMOVE.
- Ленивая загрузка и проблема N+1Lazy-заглушки и их контекст-владелец: откуда берутся LazyInitializationException и N+1, как их видеть в SQL-логе и чинить формой выборки — fetch join, EntityGraph, batch size — а не EAGER'ом.
- Запросы: JPQL, производные методы и проекцииТри источника запроса в Spring Data и их приоритет, JPQL как язык модели, пагинация с ценой count-запроса и проекции — чтение без аренды persistence context.
- Транзакции и блокировки в JPAПотерянные обновления и почему @Transactional от них не защищает: @Version с обработкой конфликтов, пессимистичные блокировки через @Lock и атомарный UPDATE как третий путь.
- Кэши HibernateТри кэша с разными контрактами: первый уровень как сам persistence context, второй — выключенная по умолчанию подписка с риском устаревания, кэш запросов — надстройка с грубой инвалидацией.
13
JDBC, пулы соединений и миграции
3 урокаСлой под ORM: JDBC как протокол доступа, пулы соединений и их настройка, границы транзакций на уровне соединения и версионирование схемы через миграции. Именно этот слой определяет поведение под нагрузкой.
- JDBC: соединение, statement, результатJDBC как слой под ORM: PreparedStatement передаёт значения отдельно от текста SQL (структурная защита от инъекций), ResultSet — курсор, а autocommit делает каждый statement транзакцией — многошаговое обернуть setAutoCommit(false).
- Пулы соединенийСоединение дорого установить — пул переиспользует; контринтуитивно пул должен быть МАЛЫМ (~(ядра2)+диски), потому что параллелизм БД ограничен, а connectionTimeout и утечки соединений задают поведение под нагрузкой.
- Миграции схемыСхема как версионированный код из упорядоченных неизменяемых миграций в schema history table (checksum сторожит), откат — компенсирующей миграцией вперёд, а zero-downtime требует совместимости с работающим кодом через expand-contract.
14
Kafka и обмен сообщениями
7 уроковАсинхронный обмен событиями через Kafka: архитектура (топики, партиции, offset), продюсеры и консьюмеры, семантики доставки, идемпотентность потребителя и обработка отравленных сообщений, интеграция через Spring Kafka.
- Зачем брокер сообщенийФакт-подписка вместо вызова-ответа: временная развязка, буфер пиков и fan-out читателей — и честная цена в виде согласованности со временем, двойной записи и распределённой отладки.
- Архитектура KafkaПартиционированный журнал: почему порядок живёт в партиции и выбирается ключом, offset принадлежит потребителю, надёжность — контракт ISR, а метаданными правит KRaft.
- Продюсер: ключи, партиционирование, гарантииАсинхронный конвейер записи: партиция по хэшу ключа, батчинг, договорная сила acks поверх ISR и идемпотентный продюсер — что он снимает, а какие дубли остаются вам.
- Консьюмеры и группыГруппа как распределённый читатель: потолок масштабирования в партициях, два таймера живости, цена ребалансировки и коммит позиции как архитектурное решение с исходами при сбое.
- Семантики доставкиТри семантики как порядок «обработать/зафиксировать позицию», транзакции Kafka с offset'ом внутри и честная граница exactly-once: внутри Kafka-конвейера — настройкой, до вашей БД — инженерией.
- Идемпотентность потребителя, retry и DLQИнженерия потребителя при at-least-once: идемпотентность через целевое состояние или таблицу обработанных сообщений, классификация сбоев, ограниченные ретраи и dead letter topic как очередь на расследование.
- Spring for Apache KafkaПаттерны модуля в коде: коммит-модель контейнера и AckMode, DefaultErrorHandler с классификацией ошибок, DLQ через DeadLetterPublishingRecoverer и защита от битой десериализации.
15
Spring Security
5 уроковБезопасность Spring-приложения: цепочка фильтров и SecurityContext, модель аутентификации, правила авторизации, stateless REST API с JWT, а также CSRF, CORS и сессии.
- Цепочка фильтров и SecurityContextSpring Security — упорядоченная цепочка сервлет-фильтров ПЕРЕД контроллером, отсекающая неавторизованное до бизнес-кода; текущий пользователь живёт в SecurityContextHolder на ThreadLocal и очищается в конце запроса.
- Модель аутентификацииАутентификация — делегирование manager→provider→UserDetailsService+PasswordEncoder; пароль хранят односторонней адаптивной функцией (bcrypt: намеренно медленной, с солью и work factor), а не быстрым SHA-256, который перебирается миллиардами/сек.
- Авторизация: URL и методыПравила authorizeHttpRequests применяются по порядку (первое совпадение выигрывает), роль — это authority с префиксом ROLE_, который hasRole добавляет сам, а @PreAuthorize даёт тонкую метод-безопасность поверх URL (defense in depth).
- Stateless API: JWT resource serverStateless REST: сервер проверяет самодостаточный JWT по ПОДПИСИ без хранилища (claims→authorities), payload лишь base64 (не шифр), а цена statelessness — токен нельзя отозвать до exp (короткий TTL+refresh или blocklist).
- CSRF, CORS и сессииТри разных механизма: CSRF защищает от авто-отправки cookie (токен на небезопасных методах, отключаем лишь при bearer-заголовке), CORS — браузерное правило cross-origin чтения (не авторизация), а сессию для API выключают STATELESS.
16
Готовность к продакшну
4 урокаЧто отличает рабочий сервис от учебного: операционные требования, Actuator и health-проверки, корректное завершение под трафиком, конфигурация окружений и обращение с секретами.
- Что значит production-readyProduction-ready — набор свойств КОДА (наблюдаемость, устойчивость, конфиг из окружения, graceful shutdown, statelessness), а не «задачи DevOps»: оркестратор лишь использует заложенное разработчиком, а 12-factor систематизирует эти свойства.
- Actuator и health-проверкиActuator открывает наружу только /health (остальное закрыть — утечка секретов); liveness проверяет внутреннее (сбой→рестарт), readiness — внешние зависимости (сбой→вывод из трафика без рестарта), а БД в liveness даёт каскадные рестарты.
- Корректное завершениеКорректное завершение — упорядоченный дренаж (SIGTERM→readiness→стоп новых→дожитие активных в grace-таймаут→закрытие ресурсов) плюс координация с балансировщиком через preStop-зазор, иначе гонка «ЛБ ещё шлёт / сервер уже закрыл» рвёт запросы при деплое.
- Конфигурация окружений и секретыConfig отделяют от кода в переменные окружения (один артефакт на все среды, лакмус open source), а секреты не хранят ни в git (история/форки), ни в образе (распаковка) — только в менеджере секретов с рантайм-внедрением и ротацией.
17
Тестирование
6 уроковПирамида тестов для Java-бэкенда: JUnit 5, тестовые дублёры с Mockito, срезы контекста Spring, интеграционные тесты с реальными зависимостями через Testcontainers и проектирование кода под тестируемость.
- Пирамида тестовМного быстрых юнит-тестов внизу, меньше интеграционных, мало медленных E2E наверху: чем выше — тем меньше (дороже/хрупче/медленнее); ice-cream cone — антипаттерн, а стратегия — толкать тесты вниз без дублирования покрытия.
- JUnit 5JUnit создаёт новый экземпляр на каждый тест (изоляция — течёт лишь static); BeforeEach на каждый vs BeforeAll один раз static, assertThrows/assertAll вместо ручного try/catch, а @ParameterizedTest покрывает много входов одним методом.
- Mockito и тестовые дублёрыПять видов дублёров, где stub проверяет состояние/результат, а mock — поведение/вызовы (verify лишь для не-наблюдаемых эффектов); over-mocking привязывает тест к реализации, поэтому мокают на границах и предпочитают проверку результата.
- Тесты Spring: срезы контекстаСрезы (@WebMvcTest, @DataJpaTest) грузят только нужный слой вместо полного @SpringBootTest; Spring кэширует контекст при одинаковой конфигурации, поэтому разнобой @MockitoBean/property плодит контексты и тормозит набор.
- Интеграционные тесты с TestcontainersСуррогаты (H2/мок) ведут себя иначе прода и дают ложную уверенность — Testcontainers поднимает реальную БД/Kafka в одноразовом контейнере (static на класс, @ServiceConnection для Spring) ценой скорости и зависимости от Docker.
- Проектирование под тестируемостьТестируемость — свойство дизайна, а не тестов: трудный тест = симптом связанного кода; инъекция зависимостей даёт шов, чистые функции тестируются без моков, а время и случайность прячут за подменяемую абстракцию (Clock), инфраструктуру — за порт.
18
Наблюдаемость
4 урокаТри канала наблюдаемости Java-сервиса: структурное логирование с корреляцией, метрики через Micrometer и распределённая трассировка с OpenTelemetry. Без них поведение под нагрузкой остаётся непрозрачным.
- Логи, метрики, трассировкиТри канала отвечают на разные вопросы: метрики — «что не так», трассировки — «где в цепочке», логи — «почему»; ни один не достаточен, а сила — в корреляции через trace id (расследование метрика→трассировка→лог).
- Структурное логированиеSLF4J-фасад + Logback с дисциплиной уровней (INFO в проде), структурный JSON для машинной агрегации вместо plain-text, MDC с trace id для корреляции строк запроса (очищать в пуле) и запрет секретов/PII в логах.
- Метрики: MicrometerТип метрики выбирают под природу величины (Counter растёт, Gauge колеблется, Timer — длительность+перцентили); теги дают срезы, но высококардинальный тег (userId/uuid) взрывает мониторинг — а RED задаёт каркас ключевых метрик.
- Распределённая трассировкаTrace — путь одного запроса из дерева спанов (где потрачено время); распространение контекста (trace id через traceparent/Kafka-заголовки) сшивает спаны разных сервисов, OpenTelemetry — стандарт, а сэмплирование держит стоимость под контролем.
19
Контейнеры и CI/CD
5 уроковДоставка Java-сервиса: образы и Dockerfile с учётом особенностей JVM, основы Kubernetes (поды, деплойменты, конфигурация), пробы и лимиты ресурсов, конвейер сборки и деплоя.
- Контейнеры: изоляция и образыКонтейнер разделяет ядро хоста (не своя ОС, как у ВМ) — потому лёгкий и быстрый; образ — стек кэшируемых слоёв из реестра, несущий приложение с зависимостями, что даёт одинаковое окружение от ноутбука до прода.
- Dockerfile для Java-приложенияMulti-stage (финал без build-инструментов), кэш-порядок (зависимости до кода), slim JRE и non-root; а JVM должна видеть cgroup-лимиты (UseContainerSupport/ MaxRAMPercentage), иначе размеряет heap от памяти хоста и падает OOMKilled.
- Kubernetes: поды, деплойменты, сервисыПод эфемерен (новый IP при пересоздании), поэтому клиенты ходят через стабильный service; deployment декларативно держит реплики, катит rolling update без даунтайма и самовосстанавливается, а ConfigMap/Secret вносят конфигурацию снаружи.
- Пробы и ресурсы подаТри пробы (liveness→рестарт, readiness→из трафика, startup→защита медленного старта JVM); requests планируют, limits ограничивают, а превышение memory limit убивает под (OOMKilled, несжимаема) против троттлинга CPU (сжимаем).
- Конвейер CI/CDАвтоматический путь коммит→прод: тесты как ворота на каждый коммит, один неизменный образ по средам (build once), миграции в конвейере (expand-contract) и деплой стратегией (rolling/blue-green/canary) с быстрым откатом на предыдущий образ.
20
Устойчивость к сбоям
4 урокаСервис среди ненадёжных соседей: режимы отказов и каскады, повторы с таймаутами, размыкатель цепи, изоляция ресурсов и осознанная деградация.
- Режимы отказов и каскадыМедленный ответ хуже быстрой ошибки — держит потоки/соединения, исчерпывает общий пул и роняет несвязанные запросы, а отказ каскадно идёт вверх по цепочке; нужен бюджет времени с таймаутами, а стратегия зависит от transient/non-transient.
- Таймауты и повторыТаймаут на каждом вызове; повтор только идемпотентных операций (иначе двойной эффект — idempotency key), с экспоненциальным backoff и джиттером против ретрай-шторма, ограниченным числом попыток и остановкой при non-transient.
- Размыкатель цепиАвтомат closed→open→half-open: при превышении порога сбоев за окно open сразу отказывает без вызова (экономит ресурсы и даёт восстановиться), half-open осторожно пробует; в отличие от retry breaker предотвращает обречённый вызов, а при open отдаёт fallback (Resilience4j).
- Изоляция и деградацияBulkhead даёт каждой зависимости свой пул/лимит — медленная топит только свой отсек, не общий пул (решение каскада); осознанная деградация возвращает урезанное вместо полного отказа, а устойчивость складывается из слоёв (таймаут+retry+breaker+bulkhead+деградация).
21
Углублённые направления
1 урокОбзорный модуль-развилка: как выбирать направление роста после уверенного владения основой — профилирование JVM, специализированные протоколы, реактивный стек, соседние языки платформы.
- Как выбирать специализациюСпециализацию выбирают от реальной потребности проекта/роли, а не от хайпа: у каждого направления (профилирование/gRPC/реактив/языки) свой сигнал «пора» и признак преждевременности, а ширина крепкой основы важнее преждевременной глубины в одном.
22
Профилирование JVM
4 урокаПоиск узких мест по измерениям: методология профилирования, Java Flight Recorder и Mission Control, CPU- и аллокационные профили с флеймграфами, чтение GC-логов и базовая настройка сборщика.
- Сначала измерениеИнтуиция о горячем месте систематически ошибается, поэтому оптимизация без профиля — угадывание; работает цикл гипотеза→метрика→профиль→изменение→повторное измерение с целью уложиться в бюджет задержки, меняя одно за раз на реальной нагрузке.
- Java Flight Recorder и Mission ControlJFR встроен в JVM с оверхедом ~1% и безопасен в проде непрерывно (чёрный ящик), пишет события (методы/аллокации/локи/GC/I/O) через флаг или jcmd, а JMC открывает .jfr и автоматически диагностирует проблемы — первый выбор прод-профилирования без агентов.
- CPU- и аллокационные профилиСэмплирующий профайлер статистически оценивает нагрузку, а флеймграф читается по ширине (шире=горячее); JVMTI-профайлеры страдают safepoint bias (время не туда), async-profiler сэмплит в любой точке и различает CPU- и аллокационный профили.
- GC-логи и базовая настройкаТри метрики GC (throughput/latency/footprint) — компромисс; GC-логи показывают, GC ли узкое место, но большинство проблем от кода (темп аллокаций/удержание), поэтому фикс — сначала код по alloc-профилю, а коллектор и кучу меняют лишь после измерения.
23
gRPC на Java
3 урокаБинарный RPC поверх HTTP/2: контракты в protobuf, генерация кода и виды вызовов, дедлайны и обработка ошибок, стриминг. Когда gRPC выигрывает у REST и какой ценой.
- Модель gRPC: protobuf и HTTP/2gRPC — contract-first бинарный RPC: .proto→codegen, protobuf кодирует номера полей (эволюция добавлением, номер неприкосновенен), поверх HTTP/2 с мультиплексированием — эффективнее REST для межсервиса, но бинарно и не для браузера.
- Сервисы, стабы и дедлайныprotoc генерирует стабы (blocking/async); deadline — абсолютная точка, пропагируемая по хопам (downstream наследует остаток) как единый бюджет цепочки, при истечении → DEADLINE_EXCEEDED с авто-отменой; ошибки — статус-коды gRPC, контекст — метаданные.
- СтримингЧетыре типа RPC (unary + server/client/bidi streaming) под форму данных (фид/загрузка чанками/интерактив); порядок сообщений внутри стрима гарантирован, HTTP/2 flow control даёт backpressure, а частичность до отмены не откатывается.
24
Reactive и WebFlux
3 урокаНеблокирующий стек: модель реактивных потоков с обратным давлением, типы Reactor (Mono, Flux), WebFlux против MVC и честное сравнение с виртуальными потоками.
- Реактивные потоки и обратное давлениеPublisher/Subscriber/Subscription с неблокирующим backpressure: потребитель задаёт темп через request(n) (в отличие от безграничных колбэков), а блокировка внутри пайплайна стопорит немногочисленные event-loop-потоки и рушит throughput.
- Reactor: Mono, Flux и операторыMono(0/1)/Flux(0..N) холодные — без subscribe ничего; map — sync 1-к-1, flatMap — async (элемент→Publisher, разворачивает); ошибки — сигнал onError вниз по цепочке (onErrorResume, не try/catch), а subscribeOn/publishOn уносят блокирующее с event loop.
- WebFlux, MVC и виртуальные потокиMVC прост, но упирается в пул потоков; WebFlux масштабирует IO малым event-loop-пулом ценой полностью неблокирующего стека и сложной отладки (спецтул, не дефолт); виртуальные потоки (Java 21+) дают масштаб при простоте MVC — выбор по нагрузке и команде, не по хайпу.
Готовьтесь по структуре, а не вслепую
Откройте курс в приложении и закрепляйте темы в тренажёре вопросов с AI-разбором.