10986
📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate. По всем вопросам @evgenycarter РКН clck.ru/3KoGeP
Совет по Spring Boot💡
Когда вам нужно настроить bean, предоставляемый Spring Boot, проверьте наличие интерфейсов *Customizer - велика вероятность, что вы сможете настроить bean, не отказываясь от автоконфигурации.
📲 Мы в MAX
👉@BookJava
Когда система на Spring Boot или Jakarta EE падает под нагрузкой, виноват не Java и не фреймворк. Почти всегда проблема — в архитектуре.
🔔 Курс «Highload Architect» учит строить высоконагруженные системы на Java: от микросервисов до монолитов. Вы разберёте весь путь запроса — от клиента до базы данных и обратно — и поймёте, какие архитектурные решения работают под сотнями тысяч и миллионами запросов в секунду.
Программа ориентирована на веб‑разработчиков, тимлидов и архитекторов ПО с опытом работы на Java.
Вы научитесь:
- оптимизировать серверы
- эффективно использовать существующие инструменты
- проектировать отказоустойчивые системы
- аргументированно защищать архитектурные решения перед командой и бизнесом.
На курсе рассмотрим типовые и нетривиальные кейсы, с которыми сталкиваются крупные сервисы.
🎁 Закрываем набор до 5.03.2026 в группу февраля — получите скидку 🔤на обучение! Подробности у менеджера.
Пройти короткое вступительное тестирование: https://vk.cc/cV1nMV
❗️ Практическое обучение проводится в прямом эфире — вебинары не являются предзаписанными.
Реклама. ООО «Отус онлайн‑образование», ОГРН 1177746618576
🔴 КАК ИДЕАЛЬНО ПРОЙТИ СОБЕС? ПОКАЖЕМ ЗАВТРА!
Каждый, проходя интервью, думал: «Ну что они хотят услышать? Я же правильно ответил! Почему меня не взяли?»
4 марта(уже завтра!) в 19:00 по мск приходи онлайн на открытое интервью, где будут собеседовать МЕНТОРА ШОРТКАТ
Как это будет:
📂 Виктор Анохин, старший разработчик из WildBerries, будет задавать реальные вопросы и задачи старшему разработчику Сергею Чамкину
📂 Сергей будет отвечать на каждый вопрос так, как это ожидает сам от вас на собеседованиях
📂 В конце можно будет задать любой вопрос Сергею и Виктору
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама.
О рекламодателе.
👩💻 Открытый урок «Реализация простого HTTP сервера на Java»
🗓 10 марта в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java-разработчик» от Otus.
Одно из основных направлений в Java разработке – разработка веб-приложений. Любому веб-разработчику важно понимать, что из себя представляет протокол HTTP и как внутри работает HTTP-сервер.
О чем поговорим:
✔ Что из себя представляет протокол HTTP и как внутри работает HTTP-сервер
✔ Как с помощью штатных средств языка Java (без использования сторонних библиотек) разработать простой HTTP сервер
✔ Вопросы возможности экономии ресурсов системы на обработку каждого запроса
Кому будет интересно:
Вебинар будет полезен тем, у кого есть уверенные знания в базе языка Java и ООП; желательно понимание базовых принципов работы с потоками ввода/вывода (java.io) и многопоточностью.
🔗 Ссылка на регистрацию: https://vk.cc/cUZH9t
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🚀 Подборка полезных IT каналов в Max
Системное администрирование, DevOps 📌
https://max.ru/i_odmin Все для системного администратора
https://max.ru/bash_srv Bash Советы
https://max.ru/sysadminof Книги для админов, полезные материалы
https://max.ru/i_odmin_book Библиотека Системного Администратора
https://max.ru/i_devops DevOps: Пишем о Docker, Kubernetes и др.
1C разработка 📌
https://max.ru/odin1c_rus Cтатьи, курсы, советы, шаблоны кода 1С
Программирование C++📌
https://max.ru/cpp_lib Библиотека C/C++ разработчика
Программирование Python 📌
https://max.ru/python_of Python академия.
https://max.ru/BookPython Библиотека Python разработчика
Java разработка 📌
https://max.ru/bookjava Библиотека Java разработчика
GitHub Сообщество 📌
https://max.ru/githublib Интересное из GitHub
Базы данных (Data Base) 📌
https://max.ru/database_info Все про базы данных
Фронтенд разработка 📌
https://max.ru/frontend_1 Подборки для frontend разработчиков
Библиотеки 📌
https://max.ru/programmist_of Книги по программированию
https://max.ru/proglb Библиотека программиста
https://max.ru/bfbook Книги для программистов
Программирование 📌
https://max.ru/bookflow Лекции, видеоуроки, доклады с IT конференций
https://max.ru/itmozg Программисты, дизайнеры, новости из мира IT
https://max.ru/php_lib Библиотека PHP программиста 👨🏼💻👩💻
Шутки программистов 📌
https://max.ru/itumor Шутки программистов
Защита, взлом, безопасность 📌
https://max.ru/thehaking Канал о кибербезопасности
https://max.ru/xakkep_1 Хакер Free
Книги, статьи для дизайнеров 📌
https://max.ru/odesigners Статьи, книги для дизайнеров
Математика 📌
https://max.ru/Pomatematike Канал по математике
https://max.ru/phismat_1 Обучающие видео, книги по Физике и Математике
Вакансии 📌
https://max.ru/progjob Вакансии в IT
Мир технологий 📌
https://max.ru/mir_teh Канал для любознательных
Бонус 📌
https://max.ru/piterspb_78 Свежие новости Санкт-Петербурга
https://max.ru/mockva_life Свежие новости Москвы
👩💻 Открытый урок «JDBC — ваш швейцарский нож для работы с данными»
🗓 26 февраля в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java Developer. Professional» от Otus.
Мастерство работы с базами данных: эффективные инструменты и лучшие практики для разработчиков.
О чём поговорим:
✔ Основы JDBC: что это такое, зачем нужно и как работает
✔ Практические примеры выполнения сложных запросов
✔ Работа с транзакциями и обработка ошибок в JDBC
✔ Оптимизация производительности при работе с данными через JDBC
Кому будет интересно:
Вебинар будет полезен разработчикам, инженерам по базам данных и архитекторам ПО, стремящимся улучшить навыки работы с базами данных и оптимизировать взаимодействие с данными.
🔗 Ссылка на регистрацию: https://vk.cc/cUM6xz
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
☸️ Kubernetes: Оркестратор вашего зоопарка
Представьте, что у вас 50 микросервисов, каждый запущен в 3 экземплярах (для надежности). Итого 150 контейнеров.
Вдруг сервер №2 сгорает. 50 контейнеров умирают.
Без K8s: Админ просыпается в 3 ночи и руками запускает их на сервере №3.
С K8s: Вы спите. K8s замечает, что "кого-то не хватает", и сам перезапускает умершие контейнеры на живых серверах за секунды. Это называется Self-healing (Самовосстановление).
Чтобы понять K8s, нужно выучить всего три главных слова: Pod, Deployment, Service.
📦 1. Pod - Атом системы
В Docker мы запускаем Контейнеры. В Kubernetes мы запускаем Поды.
• Pod - это минимальная единица. Это обертка вокруг одного (или нескольких) контейнеров.
• У Пода есть свой IP-адрес.
• Обычно: 1 Pod = 1 Контейнер с вашим app.jar.
• Редко: 1 Pod = Java App + Sidecar (например, агент логирования). Они живут вместе, как сиамские близнецы, и делят сеть.
👮♂️ 2. Deployment (Развертывание) - Начальник
Вы никогда не создаете Поды вручную. Потому что Поды смертны. Если Под умер - он умер.
Вместо этого вы создаете Deployment. Это инструкция для K8s:
"Я хочу, чтобы у меня ВСЕГДА было 3 копии моего приложения OrderService".
http://order-service и пересылает его на один из живых Подов. Ему все равно, 3 их или 30.
# 1. Описываем Deployment (Что запускать?)
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-java-app
spec:
replicas: 3 # Хочу 3 экземпляра!
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: java-app
image: my-docker-hub/app:v1 # Берем этот образ
ports:
- containerPort: 8080
---
# 2. Описываем Service (Как достучаться?)
apiVersion: v1
kind: Service
metadata:
name: my-backend-service
spec:
selector:
app: backend # Ищи Поды с меткой 'backend'
ports:
- protocol: TCP
port: 80 # Внешний порт
targetPort: 8080 # Порт контейнера
kubectl apply -f app.yaml, и магия случается.
🚀 CI/CD: Роботы делают рутину за вас
Аббревиатура состоит из двух частей, и они решают разные проблемы.
🛠 1. CI (Continuous Integration / Непрерывная интеграция)
Суть: Разработчики постоянно (несколько раз в день) сливают свой код в общую ветку (например, main или develop).
Каждый раз, когда вы делаете git push, специальный сервер (GitLab CI, GitHub Actions, Jenkins) автоматически:
1. Скачивает ваш свежий код.
2. Собирает проект (mvn clean compile).
3. Запускает все Unit и Integration тесты.
Если хоть один тест упал - сборка помечается красным крестиком (Build Failed). Код не пройдет дальше.
Итог: Ваша главная ветка в Git всегда находится в рабочем состоянии.
📦 2. CD (Continuous Delivery & Deployment)
Здесь две буквы "D", и они немного отличаются:
🔴Continuous Delivery (Доставка): Код автоматически собирается в готовый артефакт (например, Docker-образ) и кладется в хранилище. Нажать кнопку "Опубликовать на Production" должен человек (например, тимлид).
🔴Continuous Deployment (Развертывание): Полная автоматизация. Прошли тесты? Собрался образ? Он сразу же автоматически загружается на боевой сервер и заменяет старую версию. (Так делает Amazon, выкатывая обновления тысячи раз в день).
⚙️ Анатомия Пайплайна (Pipeline)
Пайплайн - это скрипт, состоящий из шагов (Stages), которые выполняются строго друг за другом. Упал предыдущий - следующий не запустится.
Типичный пайплайн для Spring Boot + Docker:
1. Lint (Проверка стиля кода, нет ли неиспользуемых импортов).
2. Test (Запуск JUnit тестов).
3. Build (Сборка `app.jar`).
4. Dockerize (Сборка Docker-образа и отправка его в Docker Registry).
5. Deploy (Команда серверу: "Скачай новый образ и перезапустись").
💻 Как это выглядит в коде? (GitHub Actions)
Вам не нужно кликать мышкой в интерфейсах. Пайплайн описывается кодом (YAML) и лежит прямо в вашем репозитории (подход Infrastructure as Code).
Вот пример простого .github/workflows/build.yml для Java-проекта:
name: Spring Boot CI/CD
on:
push:
branches: [ "main" ] # Запускать только при пуше в main
jobs:
build-and-test:
runs-on: ubuntu-latest # Выделяем виртуальную машину Linux
steps:
- uses: actions/checkout@v3 # 1. Скачиваем код из Git
- name: Установка Java 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Сборка и Тесты (Maven) # 2. Запускаем тесты и сборку
run: ./mvnw clean package
# Дальше могут быть шаги для сборки Docker и деплоя...
git push, и GitHub сам поднимет сервер, выполнит эти команды и пришлет вам письмо, если что-то сломалось.
❗️Микросервисы часто обещают масштабируемость, но на практике превращаются в сложную и хрупкую систему.
Причина почти всегда одна — неверная декомпозиция. Слишком мелкие сервисы, плотные связи, распределённые транзакции и постоянные проблемы с данными.
На открытом уроке:
- разберём, как правильно делить систему на сервисы - поговорим о границах микросервисов, ключевых шаблонах декомпозиции, подходах к взаимодействию и управлению согласованностью данных
- вы увидите, как принимать архитектурные решения не по шаблону, а с учётом бизнес-контекста, нагрузки и эволюции системы
- разберём типовые ошибки, из-за которых микросервисы теряют автономность и становятся сложнее монолита
📌 Встречаемся 18 февраля в преддверии старта курса «Highload Architect».
Зарегистрируйтесь, чтобы не пропустить: https://vk.cc/cUudMfРеклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🧅 Архитектура: От Слоев к Луковице
❌ Проблема Слоенки (Database Driven Design)
В классическом Spring-приложении зависимости идут сверху вниз:
1. Контроллер зависит от Сервиса.
2. Сервис зависит от Репозитория (Базы данных).
В чем подвох?
Ваша бизнес-логика (Сервис) намертво привязана к деталям хранения данных (БД).
🔴Хотите поменять SQL на NoSQL? Переписывайте сервис.
🔴Хотите протестировать логику? Придется мокать базу данных.
🔴Главный грех: База данных диктует, как писать бизнес-логику. А должно быть наоборот!
✅ Решение: Clean / Hexagonal / Onion
Дядя Боб, Алистер Кокберн и другие умные дядьки придумали, как перевернуть игру.
Главная идея: Зависимости должны быть направлены ТОЛЬКО внутрь, к центру.
Представьте приложение как Луковицу.
1. Ядро (Core / Domain) - Центр Вселенной
Здесь живет ваша Бизнес-логика.
🔴Сущности (User, Order).
🔴Правила (User не может быть моложе 18 лет).
🔴Правило: Здесь НЕТ фреймворков. Никакого Spring, никакого Hibernate, никакого SQL. Только чистая Java.
🔴Этот слой ничего не знает о внешнем мире.
2. Порты (Ports / Use Cases) - Граница
Ядро определяет интерфейсы (Порты), которые ему нужны для работы.
🔴Например: interface UserRepository (найти пользователя, сохранить пользователя).
🔴Заметьте: интерфейс лежит внутри домена!
3. Адаптеры (Adapters / Infrastructure) - Внешний мир
Здесь живут детали реализации.
🔴PostgresUserRepository реализует интерфейс UserRepository.
🔴RestController вызывает методы Ядра.
🔴Здесь подключается Spring, Hibernate, Kafka и всё остальное.
🔄 Инверсия Зависимостей (DIP)
Следите за руками:
1. В слоеной архитектуре: Service зависит от PostgresDao.
2. В чистой архитектуре: Service зависит от Интерфейса. А PostgresDao зависит от Интерфейса.
Оба зависят от абстракции. БД стала просто плагином. Вы можете выкинуть Postgres и поставить заглушку (In-Memory Map) - и бизнес-логика даже не заметит подмены!
⚖️ Когда что использовать?
1. Layered (Controller-Service-Repo)
• ✅ Простые CRUD-приложения.
• ✅ Админки, прототипы.
• ✅ Когда логики почти нет, просто перекладываем данные.
2. Hexagonal (Ports & Adapters)
• ✅ Сложная бизнес-логика (Банкинг, Финтех, Логистика).
• ✅ Приложение живет долго (5+ лет).
• ✅ Нужно писать много Unit-тестов для ядра, не поднимая контекст Spring.
🔥 Итог
🔴Layered: Быстро писать, сложно поддерживать. БД - главная.
🔴Clean: Дольше писать (много маппингов DTO <-> Entity), легко поддерживать. Логика - главная.
#Architecture #CleanArchitecture #Hexagonal #Spring #Java
📲 Мы в MAX
👉@BookJava
🎥 Открытый урок «Class Data Sharing и его перспективы».
🗓 17 февраля в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java Developer. Advanced».
Быстрый, лёгкий старт Java-сервисов — конкурентное преимущество. Разберём, чем поможет Class Data Sharing и где он уместен.
🔴 Завтра тестовое собеседование с Java-разработчиком
11 февраля(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.
Как это будет:
📂 Сергей Чамкин, старший разработчик из Uzum, ex-WildBerries, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Cергей будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Сергею
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама.
О рекламодателе.
🏗 SOLID - Пять заповедей программиста
Почему один проект живет 10 лет и его легко дорабатывать, а другой через полгода превращается в "Legacy", к которому страшно подходить?
Разница в соблюдении принципов SOLID.
Это аббревиатура из 5 правил, сформулированных Робертом Мартином (Дядя Боб). Если вы нарушаете их - ваш код "гниет".
Давайте разберем каждую букву.
1️⃣ S - Single Responsibility Principle (Единственная ответственность)
"У класса должна быть только одна причина для изменения."
OrderService делает всё:OrderCalculator (считает).OrderRepository (сохраняет).EmailNotificationService (шлет письма).PdfGenerator (печатает).OrderService теперь просто дирижер, который вызывает эти компоненты."Программные сущности должны быть открыты для расширения, но закрыты для модификации."
if (deliveryType == "DHL") { ... }
else if (deliveryType == "Post") { ... }
// Пришла задача добавить FedEx? Придется лезть сюда и добавлять else if!
interface DeliveryStrategy { void deliver(); }
class DhlDelivery implements DeliveryStrategy { ... }
class PostDelivery implements DeliveryStrategy { ... }
// Нужен FedEx? Просто создаем НОВЫЙ класс, не трогая старые!
class FedExDelivery implements DeliveryStrategy { ... }
"Наследники должны без проблем заменять родителей."
Bird с методом fly(), а вы создали наследника Penguin (Пингвин), который при вызове fly() бросает ошибку (потому что пингвины не летают) - вы нарушили LSP."Много маленьких интерфейсов лучше, чем один огромный."
Worker имеет методы work() и eat().Robot. Роботы работают, но не едят.eat() и оставить его пустым или кинуть ошибку. Это мусор.Workable и Eatable.Workable."Зависьте от абстракций, а не от конкретики."
Service не должен зависеть от PostgresRepository. Он должен зависеть от интерфейса Repository.
👮♂️ Spring Security: Фейсконтроль для вашего API
Spring Security - это не просто библиотека, это мощный фреймворк, который встает стеной между интернетом и вашим контроллером.
Его работа строится на концепции Filter Chain (Цепочка фильтров). Каждый запрос проходит через серию проверок: "Есть ли токен?", "Валиден ли он?", "Есть ли права?".
🔑 Authentication vs Authorization
Два слова, которые путают все джуниоры.
1. Authentication (Аутентификация): "Кто ты?"
🔴Ввод логина/пароля.
🔴Проверка отпечатка пальца.
🔴Ответ: 401 Unauthorized (если не знаем, кто это).
2. Authorization (Авторизация): "А что тебе можно?"
🔴Ты Вася (мы тебя узнали), но ты хочешь удалить базу данных. Тебе нельзя.
🔴Ответ: 403 Forbidden (знаем кто ты, но не пустим).
🎫 JWT (JSON Web Token) - Паспорт туриста
В микросервисах мы не храним состояние пользователя на сервере (Stateless). Вместо этого, при логине мы выдаем пользователю Токен.
JWT - это строка из трех частей, разделенных точками: Header.Payload.Signature.
🔴Payload: Полезные данные (User ID, Role, Email).
🔴Signature: Цифровая подпись сервера. Гарантирует, что хитрый хакер не поменял в токене роль USER на ADMIN.
Как это работает:
1. Клиент шлет Логин/Пароль -> Сервер проверяет и отдает JWT.
2. Клиент сохраняет JWT (обычно в LocalStorage браузера).
3. При каждом запросе клиент прикрепляет JWT в заголовок:Authorization: Bearer <token>
4. Сервер видит токен, проверяет подпись и пускает (не ходя в базу данных!).
🛡 Настройка (Spring Boot 3.x)
Раньше мы наследовались от WebSecurityConfigurerAdapter. Забудьте, этот класс Deprecated.
Сейчас мы просто объявляем бин SecurityFilterChain.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable) // Для REST API отключаем
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // Никаких сессий!
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll() // Логин доступен всем
.requestMatchers("/admin/**").hasRole("ADMIN") // Админка только админам
.anyRequest().authenticated() // Всё остальное - только с токеном
)
// Добавляем наш кастомный фильтр для проверки JWT
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
application.yaml, но под капотом там огромная машина стандартов.
🎥 Открытый урок «Eclipse Memory Analyzer (MAT): помощь в работе с heap».
🗓 04 февраля в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java Developer. Advanced».
Без MAT сложно найти причины OutOfMemoryError и утечек. Разберём базу и полезные приёмы анализа heap dump.
От 1 до миллиона пользователей (Масштабирование)
Представьте: вы написали крутой стартап. Он крутится на дешевом сервере за $5 в месяц. База данных, бэкенд и фронтенд лежат в одном месте.
Вдруг о вас снимает ролик популярный блогер. Трафик вырастает в 10,000 раз. Сервер ложится через 3 секунды от OutOfMemoryError. Бизнес теряет деньги. Что делать?
У вас есть два пути масштабирования (Scaling).
📈 1. Вертикальное масштабирование (Scale Up)
Это самый простой и интуитивный подход.
Сервер не справляется? Давайте купим сервер мощнее! Было 2 ГБ оперативки - поставим 128 ГБ. Был 1 ядерный процессор - купим 64-ядерный.
🔴Плюсы: Не нужно менять ни строчки кода. Всё просто работает быстрее.
🔴Минусы: 1. Физический предел. Вы не можете купить сервер с бесконечной памятью. Самый мощный сервер рано или поздно закончится.
2. SPOF (Single Point of Failure). Единая точка отказа. Каким бы мощным ни был сервер, если уборщица выдернет шнур питания в дата-центре - ваш бизнес остановится.
🌐 2. Горизонтальное масштабирование (Scale Out)
Это путь настоящих джедаев и BigTech-компаний.
Вместо покупки одного суперкомпьютера за миллион долларов, мы покупаем 1000 дешевых обычных серверов и заставляем их работать вместе.
🔴Плюсы: Масштабирование практически бесконечно. Упал один сервер? Ничего страшного, трафик подхватят остальные 999.
🔴Минусы: Архитектура становится в разы сложнее. Появляется куча новых проблем: как делить трафик, как синхронизировать данные, как хранить сессии.
⚖️ 3. Балансировщик нагрузки (Load Balancer)
Допустим, мы выбрали горизонтальное масштабирование и запустили 5 одинаковых серверов с нашим Spring Boot приложением.
Но пользователи (клиенты) знают только один домен: mysite.com. Как сделать так, чтобы запросы распределялись между этими пятью серверами равномерно?
Перед нашими серверами встает Load Balancer (Балансировщик) - например, Nginx, HAProxy или облачный AWS ALB.
Пользователь стучится в Балансировщик, а тот перенаправляет запрос на наименее загруженный сервер.
🔴Round Robin: Самый простой алгоритм. Запросы раздаются по кругу: первому серверу, второму, третьему, первому, второму...
🔴Least Connections: Запрос уходит на тот сервер, у которого сейчас меньше всего активных соединений.
🧠 4. Главное правило: Stateless (Без состояния)
Горизонтальное масштабирование ломает старый подход к хранению сессий.
Если пользователь залогинился и попал на Сервер №1, этот сервер сохранил его данные в оперативной памяти. При следующем клике балансировщик может кинуть пользователя на Сервер №2. А Сервер №2 скажет: "Я тебя не знаю, авторизуйся заново".
Решение: Серверы приложения должны быть "глупыми" и ничего не помнить (Stateless).
Состояние должно храниться в общем внешнем хранилище (например, в Redis, как мы обсуждали в прошлых сезонах), либо передаваться прямо в запросе с помощью токенов (JWT).
🔥 Итог
1. Vertical Scaling (вверх) - купить "железо" помощнее. Дорого, есть предел.
2. Horizontal Scaling (вширь) - поставить больше дешевых серверов. Требует изменения архитектуры.
3. Load Balancer - дирижер, который распределяет трафик между клонами.
4. Stateless - обязательное условие. Приложение не должно хранить локальное состояние.
#SystemDesign #Architecture #Scaling #LoadBalancing #Backend
📲 Мы в MAX
👉@BookJava
☕ Функциональные интерфейсы: Магия за кулисами Лямбд
Если вы используете лямбды (а вы наверняка их используете), значит, вы работаете с функциональными интерфейсами. Давайте разберем, что это такое, зачем нужна аннотация и какие интерфейсы вы обязаны знать наизусть.
🎯 Что это такое?
Функциональный интерфейс - это интерфейс, который содержит ровно один абстрактный метод.
Именно это ограничение позволяет компилятору превращать лямбда-выражение в экземпляр этого интерфейса.
💙 Примечание: В интерфейсе может быть сколько угодно default или static методов. Главное - только один абстрактный.
📝 Аннотация @FunctionalInterface
Ставить её над интерфейсом не обязательно, но хорошим тоном считается ставить.
Зачем? Она работает как защита от дурака: если вы или ваш коллега случайно добавите второй абстрактный метод в интерфейс, компилятор сразу выдаст ошибку, не дожидаясь падения кода в местах использования лямбд.
🧰 Шпаргалка: "Великолепная четверка"
В пакете java.util.function уже есть готовые интерфейсы на 99% случаев жизни. Не пишите свои велосипеды, пока не выучите эти:
1. Predicate <T>
💙Что делает: Проверяет условие.
💙 Метод: boolean test(T t)
💙 Где нужен: Фильтрация стримов (`filter`), проверки.
2. Consumer <T>
💙 Что делает: "Потребляет" объект, ничего не возвращая.
💙 Метод: void accept(T t)
💙 Где нужен: Вывод на экран, запись в БД, forEach.
3. Supplier <T>
💙 Что делает: "Поставляет" объект (из ниоткуда), ничего не принимая.
💙 Метод: T get()
💙 Где нужен: Ленивая инициализация, генерация значений, orElseGet.
4. Function <T, R>
💙 Что делает: Превращает объект типа T в объект типа R.
💙 Метод: R apply(T t)
💙 Где нужен: Преобразование данных, map в стримах.
💻 Пример в коде
@FunctionalInterface
interface Converter {
int stringToInt(String s);
}
public class Main {
public static void main(String[] args) {
// 1. Создаем реализацию через лямбду
Converter converter = str -> Integer.parseInt(str);
// 2. Использование стандартного Consumer
java.util.function.Consumer<Integer> print = x -> System.out.println("Result: " + x);
int result = converter.stringToInt("123");
print.accept(result); // Вывод: Result: 123
}
}
java.util.function, чтобы ваш код был понятен другим разработчикам без лишних документаций.
Совет Spring Framework💡
Вы можете инжектировать (autowire) бины, которые могут отсутствовать, обернув их в java.util.Optional. Таким образом вы сообщаете, что этот бин является необязательным, избегаете исключения, если он не существует, и можете аккуратно обработать его отсутствие с помощью Optional API.
📲 Мы в MAX
👉@BookJava
🕸️ Распределенная Трассировка: Ищем "бутылочное горлышко" (Zipkin & Jaeger)
Если логи это текст, а метрики это графики, то трассировка это диаграмма Ганта (каскад) для каждого отдельного HTTP-запроса.
🧩 Главные понятия: Trace и Span
Вся магия строится на двух терминах:
1. Trace (След/Трасса): Это весь путь запроса от клика пользователя в браузере до самого глубокого ответа от базы данных. У него есть единый Trace ID.
2. Span (Пролет/Отрезок): Это один конкретный шаг внутри Трассы. Например, "поход в базу данных" - это один Span. "HTTP-запрос в сервис оплаты" - это другой Span. У каждого спана есть свой Span ID и информация о том, сколько миллисекунд он выполнялся.
🕵️♂️ Как сервисы передают ID друг другу?
Когда Gateway принимает запрос, он генерирует Trace ID (например, abc-123) и кладет его в HTTP-заголовки (обычно используется стандарт W3C traceparent или b3).
Когда Gateway вызывает OrderService, он передает эти заголовки дальше. OrderService читает их и понимает: "Ага, я часть большой трассы abc-123".
Вам не нужно писать этот код руками! В Spring Boot 3 за это отвечает библиотека Micrometer Tracing. Она автоматически перехватывает все вызовы через RestTemplate, WebClient, Feign и запросы к БД, вклеивая туда нужные ID.
🛠 Настройка (Spring Boot 3 + Zipkin)
Вам нужно добавить всего пару зависимостей в pom.xml:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
application.yaml указываем адрес сервера Zipkin:
management:
tracing:
sampling:
probability: 1.0 # Отправлять 100% запросов (на проде обычно ставят 0.1, чтобы не грузить сеть)
zipkin:
tracing:
endpoint: "http://localhost:9411/api/v2/spans"
Trace ID (который вы скопировали из логов в Kibana) и видите красивую цветную лесенку:Gateway (Всего: 5.0s)OrderService.create() (4.9s)DB: INSERT order (0.05s)PaymentService.pay() (4.8s) 👈 АГА! ВОТ КТО ТОРМОЗИТ!External Bank API (4.75s)ConnectionTimeoutException).
🪵 ELK Stack: Гугл для ваших логов
ELK - это аббревиатура трех инструментов, которые стали стандартом индустрии:
1. Elasticsearch (База данных).
2. Logstash (Труба).
3. Kibana (Интерфейс).
В современном мире к ним часто добавляется Filebeat (или Fluentd), и стек превращается в EFK.
🏗 Как это работает? (Архитектура)
Представьте конвейер:
1. Java App: Просто пишет логи в консоль (STDOUT) в формате JSON.
2. Filebeat: Маленький агент, который стоит на каждом сервере (или в каждом Поде K8s). Он "слушает" консоль приложения, собирает строки и отправляет их дальше.
3. Logstash: (Опционально) Фильтрует и обрабатывает логи (например, скрывает пароли, парсит даты).
4. Elasticsearch: Хранит терабайты логов и позволяет искать по ним мгновенно.
5. Kibana: Сайт, на который вы заходите, чтобы увидеть графики и поиск.
📝 1. JSON Logging: Забудьте про текст
Обычные логи Java выглядят так:2023-10-25 INFO [main] UserService: User created
Это плохо. Компьютеру сложно понять, где тут дата, а где сообщение.
В микросервисах мы пишем логи в JSON:
{
"timestamp": "2023-10-25T10:00:00",
"level": "INFO",
"logger": "UserService",
"message": "User created",
"user_id": "123",
"trace_id": "abc-987"
}
logstash-logback-encoder и настраиваем logback-spring.xml. Теперь ваши логи это структурированные данные, по которым можно фильтровать!trace_id="abc-987" и видите все логи всех сервисов, которые участвовали в этом конкретном запросе.
// В фильтре на входе запроса
MDC.put("userId", request.getHeader("X-User-ID"));
// Где-то глубоко в сервисе
log.info("Заказ создан");
// В JSON-логе автоматически появится поле "userId": "123"
// В конце
MDC.clear();
grep.
📊 Мониторинг: Пульс вашего приложения
В мире мониторинга Java есть три главных игрока:
1. Spring Boot Actuator (Генерирует метрики).
2. Prometheus (Собирает и хранит их).
3. Grafana (Рисует красивые графики).
Давайте разберем, как они работают вместе.
1️⃣ Spring Boot Actuator & Micrometer
Сначала нужно научить приложение рассказывать о себе.
В Spring Boot это делается добавлением двух зависимостей: Actuator и Micrometer.
Micrometer это как SLF4J, только для метрик. Это фасад. Вы пишете код один раз, а Micrometer умеет отправлять эти данные хоть в Prometheus, хоть в Datadog, хоть в New Relic.
В pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
application.yaml:
management:
endpoints:
web:
exposure:
include: prometheus, health, info
/actuator/prometheus, вы увидите не JSON, а скучный текст:
# HELP jvm_memory_used_bytes The amount of used memory
# TYPE jvm_memory_used_bytes gauge
jvm_memory_used_bytes{area="heap",id="G1 Eden Space",} 2.5165824E7
http_server_requests_seconds_count{uri="/users",status="200",} 452.0
/actuator/prometheus.MeterRegistry.
@Service
public class OrderService {
private final Counter orderCounter;
public OrderService(MeterRegistry registry) {
// Создаем счетчик "orders.created"
this.orderCounter = registry.counter("orders.created");
}
public void createOrder(Order order) {
repo.save(order);
orderCounter.increment(); // +1 к метрике
}
}
/actuator/prometheus).
⚡️ Хотите освоить Backend-разработку с нуля, но не знаете, с чего начать? Java — это лучший старт!
Курс «Java-разработчик» от OTUS — это 15 месяцев обучения, которое откроет вам двери в мир Java-разработки. Программа включает живые лекции от опытных наставников и актуальные инструменты, такие как IntelliJ IDEA, Spring, Hibernate, Docker и Kubernetes. Мы разбираем всё: от синтаксиса Java до создания серверных приложений и работы с базами данных.
После курса вы сможете:
✔ создавать серверные приложения на Java;
✔ работать с реляционными и NoSQL базами данных;
✔ осваивать и применять фреймворки Spring, Hibernate, Docker, Kafka, RabbitMQ;
✔ претендовать на позицию Middle Java Developer в крупных компаниях.
Оставьте заявку и получите скидку на большое обучение «Java-разработчик»: https://vk.cc/cUAK9u
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🧹 Гигиена кода: Рефакторинг и Технический долг
Почему проекты умирают? Не из-за плохой идеи, а из-за того, что стоимость добавления новой фичи становится выше, чем прибыль от неё.
Разработчики тратят 90% времени на чтение и распутывание старого кода, и только 10% - на написание нового.
💳 1. Technical Debt (Технический долг)
Это метафора, придуманная Уордом Каннингемом.
🔴Суть: Вы берете "кредит" у качества кода, чтобы выпустить фичу быстрее ("Сделаем костыль, потом поправим").
🔴Проценты: Каждая минута, потраченная на борьбу с этим костылем в будущем.
🔴Банкротство: Момент, когда код настолько запутан, что проще всё переписать с нуля, чем добавить кнопку.
Главное правило: Долги надо возвращать. Выделяйте 20% времени в спринте на рефакторинг.
2. Code Smells (Запахи кода)
Как понять, что код "тухнет"? По запаху.
Это не баги (код работает), это признаки плохого дизайна.
Топ-5 самых вонючих мест:
1. God Object (Божественный объект): Класс Manager или Utils, который знает всё и делает всё. Нарушает SRP (Single Responsibility Principle).
2. Long Method (Длинный метод): Если метод не помещается на один экран монитора - это запах.
3. Magic Numbers (Магические числа):
• Плохо: if (status == 7) ... (Что такое 7? Счастливое число?)
• Хорошо: if (status == STATUS_ACTIVE) ...
4. Duplicated Code (Дублирование): Copy-Paste это зло. Если вы нашли ошибку в одном месте, вам придется искать её копии по всему проекту. Принцип DRY (Don't Repeat Yourself).
5. Feature Envy (Зависть к чужим данным): Метод класса А постоянно обращается к полям класса Б. Скорее всего, этот метод должен жить в классе Б.
🛠 3. Refactoring (Рефакторинг)
Это процесс изменения структуры кода без изменения его поведения.
Золотое правило Бойскаута:
"Оставь место стоянки чище, чем оно было до твоего прихода."
public void process(Order o) {
// Magic Number + Long Method + Feature Envy
if (o.getItems().size() > 0 && o.getStatus() == 1) {
double total = 0;
for (Item i : o.getItems()) {
total += i.getPrice() * 1.2; // Что такое 1.2? НДС? Наценка?
}
System.out.println("Total: " + total); // Hardcoded output
}
}
private static final double TAX_RATE = 1.2;
private static final int STATUS_ACTIVE = 1;
public void printOrderTotal(Order order) {
if (isEligibleForProcessing(order)) {
double total = calculateTotal(order);
outputResult(total);
}
}
private boolean isEligibleForProcessing(Order order) {
return !order.isEmpty() && order.getStatus() == STATUS_ACTIVE;
}
private double calculateTotal(Order order) {
return order.getItems().stream()
.mapToDouble(item -> item.getPrice() * TAX_RATE)
.sum();
}
🧠 Domain Driven Design: Программируем на языке бизнеса
Главная проблема IT: Разработчики говорят на языке таблиц (INSERT, Foreign Key, DTO), а бизнес на языке денег и процессов («Провести проводку», «Списать остаток»).
DDD (Предметно-ориентированное проектирование) - это попытка убрать переводчика.
🗣 1. Ubiquitous Language (Единый язык)
Это фундамент. Код должен звучать так же, как речь эксперта.
🔴Плохо (CRUD-мышление):
• Бизнес: "Клиент сменил адрес доставки".
• Код: user.setAddress("New York"); userRepo.save(user);
🔴Хорошо (DDD):
• Код: user.relocateTo(new Address("New York"));
Методы должны называться глаголами бизнеса, а не сеттерами.
📦 2. Bounded Context (Ограниченный контекст)
Самая большая ошибка новичка - создать один класс Product на всё приложение.
🔴Для Продаж: Товар - это цена, описание, картинки.
🔴Для Склада: Товар - это вес, габариты, номер полки.
🔴Для Бухгалтерии: Товар - это актив, амортизация, инвентарный номер.
В DDD мы не делаем монстра. Мы создаем разные модели для разных контекстов.
🔴SalesContext.Product
🔴WarehouseContext.StockItem
Эти модели могут даже иметь разные ID и общаться друг с другом только через события (Kafka).
🏗 3. Tactical DDD (Строительные блоки)
Как писать код внутри контекста?
A. Entity (Сущность)
Объект, у которого есть Identity (ID).
Если у двух людей одинаковое имя, это все равно два разных человека (разные ID).
🔴Пример: User, Order.
🔴Они живут долго, меняют свое состояние, но остаются собой.
B. Value Object (Объект-значение)
Объект, который определяется только своими данными. У него нет ID. Он неизменяем (Immutable).
🔴Пример: Money, Address, Color.
🔴Если у меня есть 100 рублей и у вас 100 рублей - это одни и те же 100 рублей. Нам не важно, какая именно это купюра.
🔴Правило: Не используйте примитивы! Вместо String email используйте класс EmailAddress. Там можно спрятать валидацию.
C. Aggregate (Агрегат)
Это кластер объектов, которые живут и умирают вместе.
🔴Пример: Заказ (Order) + Позиции заказа (OrderItems).
🔴Aggregate Root (Корень): Это главный объект (Order).
🔴Правило: Извне можно обращаться только к Корню. Нельзя получить ссылку на OrderItem и изменить его цену напрямую. Вы должны сказать: order.changeItemPrice(...). Это гарантирует целостность данных.
🩸 Anemic vs Rich Model (Анемичная vs Богатая модель)
❌ Анемичная (Стандартный Spring):
Класс — это просто мешок с геттерами и сеттерами. Вся логика лежит в Service.
// Service
public void completeOrder(Long id) {
Order order = repo.findById(id);
if (order.getStatus() != PAID) throw ...
order.setStatus(COMPLETED); // Кто угодно может поменять статус!
repo.save(order);
}
// Entity
public void complete() {
if (this.status != PAID) throw new DomainException("Не оплачено!");
this.status = COMPLETED;
// Можно даже вернуть событие OrderCompletedEvent
}
// Service
public void completeOrder(Long id) {
Order order = repo.findById(id);
order.complete(); // Вся бизнес-логика внутри
repo.save(order);
}
🧠 Поведенческие паттерны: Стратегия, Наблюдатель, Цепочка
Эти паттерны помогают избежать спагетти-кода, где один класс управляет всем миром через гигантские if-else.
🗺 1. Strategy (Стратегия)
Суть: Позволяет менять алгоритм поведения объекта прямо "на лету", во время выполнения программы.
Это убийца бесконечных if (type == "CARD") { ... } else if (type == "PAYPAL") { ... }.
Аналогия: Навигатор. Вы строите маршрут из точки А в точку Б. Стратегия - это способ передвижения:
🔴Пешком (один алгоритм).
🔴На машине (другой алгоритм).
🔴На автобусе (третий алгоритм).
Цель одна, пути реализации разные.
Код:
// Общий интерфейс
interface RouteStrategy {
void buildRoute(String a, String b);
}
// Конкретные стратегии
class RoadStrategy implements RouteStrategy { ... }
class WalkingStrategy implements RouteStrategy { ... }
// Контекст (Навигатор)
class Navigator {
private RouteStrategy strategy;
public void setStrategy(RouteStrategy strategy) {
this.strategy = strategy; // Меняем на лету!
}
public void buildRoute(String a, String b) {
strategy.buildRoute(a, b);
}
}
class NewsAgency {
private List<Channel> channels = new ArrayList<>();
public void subscribe(Channel channel) {
channels.add(channel);
}
public void broadcast(String news) {
for (Channel channel : channels) {
channel.update(news); // Уведомляем всех!
}
}
}
CorsFilter (проверяет домен) -> JwtFilter (проверяет токен) -> UsernamePasswordFilter (проверяет логин).
abstract class SupportHandler {
protected SupportHandler next;
public void setNext(SupportHandler next) { this.next = next; }
public void handleRequest(String issue) {
if (canHandle(issue)) {
solve();
} else if (next != null) {
next.handleRequest(issue); // Передаем следующему
}
}
}
🏗 Структурные паттерны: Адаптер, Декоратор, Прокси
Когда вы пытаетесь соединить старую библиотеку с новым кодом или добавить логирование, не переписывая половину проекта - вы используете эти паттерны.
🔌 1. Adapter (Адаптер)
Суть: Делает несовместимые интерфейсы совместимыми.
Это как переходник для розетки. У вас вилка американская, а розетка европейская. Адаптер позволяет им работать вместе.
Где нужен: Когда есть старый класс (Legacy), который нельзя менять, но его нужно использовать в новом коде.
Пример: У нас есть система, которая понимает только KM/H (километры), а внешняя библиотека выдает скорость в MPH (мили).
// 1. Наш интерфейс (чего мы ждем)
interface Movable { double getSpeed(); } // км/ч
// 2. Чужой класс (что есть)
class Bugatti {
double getSpeedMph() { return 268; }
}
// 3. Адаптер (Переводчик)
class MovableAdapter implements Movable {
private Bugatti bugatti;
public MovableAdapter(Bugatti bugatti) {
this.bugatti = bugatti;
}
@Override
public double getSpeed() {
return convertMphToKmph(bugatti.getSpeedMph());
}
}
CoffeeWithMilkAndSugar, мы берем Coffee и заворачиваем его в Milk, а потом в Sugar.
// Базовый кофе
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getCost()); // 10$
// Добавили молоко (Обернули)
coffee = new MilkDecorator(coffee);
System.out.println(coffee.getCost()); // 12$
// Добавили сахар (Обернули еще раз)
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getCost()); // 13$
java.io это используется повсюду: new BufferedReader(new FileReader(file)).
interface Image { void display(); }
class RealImage implements Image {
public RealImage(String file) { loadFromDisk(file); } // Долгая операция!
public void display() { System.out.println("Displaying..."); }
}
class ProxyImage implements Image {
private RealImage realImage;
private String file;
public ProxyImage(String file) { this.file = file; }
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(file); // Грузим только сейчас!
}
realImage.display();
}
}
@Transactional, Spring создает прокси вокруг вашего сервиса, открывает транзакцию, вызывает ваш метод, а потом закрывает транзакцию.
🏗 Порождающие паттерны: Как рождаются объекты?
Создать объект просто: User u = new User().
А если у объекта 20 полей? А если нам нужен только один экземпляр на всё приложение? А если мы не знаем заранее, какой именно класс нам нужен?
Тут на сцену выходят паттерны.
1️⃣ Singleton (Одиночка)
Суть: Гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа.
Где нужен: Логгеры, Конфигурация, Пул соединений с БД.
Как реализовать:
1. Скрываем конструктор (private).
2. Создаем статическое поле с экземпляром.
3. Возвращаем его через статический метод.
public class Database {
// Единственный экземпляр
private static Database instance;
private Database() {} // Никто не создаст объект извне
public static synchronized Database getInstance() {
if (instance == null) {
instance = new Database();
}
return instance;
}
}
age, а где height).new User("Alex", null, true, "admin", 25, null);
User user = User.builder()
.name("Alex")
.age(25)
.role("ADMIN")
.active(true)
.build();
@Builder над классом.
// 1. Интерфейс
interface Notification { void send(String msg); }
// 2. Реализации
class EmailNotification implements Notification { ... }
class SmsNotification implements Notification { ... }
// 3. Фабрика (Решает, что создать)
class NotificationFactory {
public static Notification create(String type) {
return switch (type) {
case "EMAIL" -> new EmailNotification();
case "SMS" -> new SmsNotification();
default -> throw new IllegalArgumentException("Unknown type");
};
}
}
// Клиентский код (не знает про классы Email/Sms, знает только интерфейс)
Notification notification = NotificationFactory.create("SMS");
notification.send("Hello!");
А вы справитесь с тестом по HighLoad?
Как пройти путь от разработчика до архитектора высоконагруженных систем для работы с крупными проектами?
Пройдите тест, проверьте свои знания для обучения на курсе «Highload Architect» от OTUS. А так же и получите скидку 🎁 до 15.02.2026 - подробности у менеджера.
➡️ Пройти Тест https://vk.cc/cU6fLb
На курсе вы освоите проектирование масштабируемых и отказоустойчивых систем, оптимизацию производительности, работу с современными инструментами для создания высоконагруженных решений и лучшие практики разработки серверных приложений.
❗️Практическое обучение проводится в прямом эфире — вебинары не являются предзаписанными.
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🚀 Redis + Spring Cache: Турбо-наддув для бэкенда
Самая медленная часть любого приложения это ввод-вывод (I/O). Поход в базу данных (Postgres/MySQL) это "долго" (миллисекунды). Поход в оперативную память это "мгновенно" (наносекунды).
Redis - это база данных, которая хранит всё в оперативной памяти (In-Memory). Она идеально подходит на роль кэша.
🧠 Как работает Spring Cache?
Spring предоставляет крутую абстракцию. Вам не нужно писать код для подключения к Redis в каждом методе. Вы просто вешаете аннотации, а Spring сам перехватывает вызов метода.
Алгоритм @Cacheable:
1. Вызывается метод getUser(1).
2. Spring лезет в кэш (Redis) по ключу user::1.
3. Если данные есть (Cache Hit): Spring НЕ выполняет код метода, а сразу возвращает данные из кэша.
4. Если данных нет (Cache Miss): Spring выполняет метод (идет в БД), берет результат, кладет его в кэш и отдает вам.
🛠 Настройка (2 шага)
1. Зависимости
В pom.xml добавляем стартер для кэша и драйвер Redis:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
@EnableCaching.@Cacheable — "Запомни меня"get, find).
@Service
public class UserService {
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
// Имитация долгого запроса в БД (3 секунды)
simulateSlowService();
return userRepository.findById(id).orElseThrow();
}
}
@CacheEvict - "Забудь меня"
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
getUserById(id) Spring увидит, что кэш пуст, и снова сходит в БД за свежими данными.@CachePut - "Обнови меня"ConcurrentHashMap прямо в Java?"HashMap съест всю память JVM, и приложение упадет (OutOfMemory). Redis живет отдельно.HashMap. Кэш будет рассинхронизирован. Redis это общий кэш для всех инстансов.HashMap очищается. Redis (обычно) работает на отдельном сервере и хранит данные даже при рестарте вашего кода.Serializable, либо (что правильнее) нужно настроить Jackson Serializer, чтобы хранить данные в Redis в читаемом JSON-формате.@Cacheable.@CacheEvict.
📨 Apache Kafka: Нервная система микросервисов
Представьте, что вы заказали пиццу.
🔴REST подход: Вы стоите у прилавка и смотрите на повара, пока он не закончит. Вы не можете отойти. Если повар уснул - вы застряли.
🔴Kafka подход: Вы бросаете чек в коробку "Заказы" и уходите по своим делам. Повар возьмет заказ, когда освободится. Когда пицца будет готова, он положит её на стол выдачи, и вы получите уведомление.
Kafka - это распределенный лог событий. Это не просто очередь, это история всего, что произошло в системе.
🧩 Основные понятия
1. Topic (Топик) - Это "папка" или канал, куда падают сообщения. Например: orders-topic, email-topic.
2. Producer (Продюсер) - Тот, кто пишет сообщения в топик (например, сервис Заказов).
3. Consumer (Консьюмер) - Тот, кто читает сообщения (например, сервис Уведомлений или Склад).
4. Broker (Брокер) - Сервер Kafka, который хранит эти данные.
🚀 Главная фишка: Fire and Forget
Когда OrderService создает заказ, ему плевать, работает ли сейчас сервис отправки SMS или сервис начисления бонусов.
Он просто кидает событие OrderCreated в Kafka и мгновенно возвращает ответ пользователю "Заказ принят".
Сервисы-подписчики (Consumers) разгребут эти сообщения в своем темпе. Если сервис SMS упал, он поднимется через час, прочитает топик с того места, где остановился, и дошлет все смски. Данные не пропадут.
💻 Spring Kafka: Как писать код?
В Spring Boot работа с Kafka максимально упрощена.
1. Настройка (application.yaml)
spring:
kafka:
bootstrap-servers: localhost:9092 # Адрес брокера
consumer:
group-id: my-group # Важно для масштабирования
KafkaTemplate.
@Service
@RequiredArgsConstructor
public class OrderProducer {
private final KafkaTemplate<String, String> kafkaTemplate;
public void sendOrderEvent(String orderId) {
// Отправляем в топик "orders"
kafkaTemplate.send("orders", orderId);
System.out.println("Сообщение отправлено: " + orderId);
}
}
@KafkaListener.
@Service
public class NotificationConsumer {
@KafkaListener(topics = "orders", groupId = "notification_group")
public void listen(String orderId) {
System.out.println("Получено событие для заказа: " + orderId);
// Тут логика отправки email/sms
sendEmail(orderId);
}
}