Библиотека Java разработчика
10.5K subscribers
1.17K photos
594 videos
58 files
1.51K links
📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate.


По всем вопросам @evgenycarter

РКН clck.ru/3KoGeP
Download Telegram
🧪 Тестирование в Spring Boot: Спите спокойно

Глобально тесты делятся на два лагеря:

1. Unit-тесты (Модульные): Быстрые, изолированные. Проверяем только логику одного класса (обычно Service). Никаких баз данных и поднятия Spring Context.
2. Integration-тесты (Интеграционные): Проверяем, как компоненты работают вместе (Controller + Service + DB). Здесь поднимается Spring Context.

1. Unit-тесты: Изоляция и Скорость

Когда вы тестируете UserService, вам не нужно, чтобы он реально лез в базу данных. Вам нужно проверить: "Если репозиторий вернет null, выбросит ли сервис ошибку?"

Для этого мы используем Mockito - библиотеку, которая создает "фейковые" объекты (моки).

Ключевые аннотации:

🔴@ExtendWith(MockitoExtension.class) - включаем Mockito.
🔴@Mock - "Создай фейковый объект" (например, UserRepository).
🔴@InjectMocks - "Создай тестируемый объект (UserService) и вставь в него моки".

💻 Пример Unit-теста:


@ExtendWith(MockitoExtension.class) // 1. Включаем Mockito
class UserServiceTest {

@Mock
private UserRepository userRepository; // Фейк

@InjectMocks
private UserService userService; // Реальный сервис с фейком внутри

@Test
void shouldReturnUser_WhenExists() {
// GIVEN (Дано) - Настраиваем поведение мока
User mockUser = new User("Alex");
// "Если кто-то вызовет findById(1), верни mockUser"
Mockito.when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));

// WHEN (Когда) - Вызываем метод сервиса
User result = userService.findById(1L);

// THEN (Тогда) - Проверяем результат
Assertions.assertEquals("Alex", result.getName());
// Проверяем, что метод репозитория действительно вызывался 1 раз
Mockito.verify(userRepository, times(1)).findById(1L);
}
}



🐢 2. Integration-тесты: Тяжелая артиллерия

Иногда нужно проверить, правильно ли мапится JSON в контроллере или работает ли SQL-запрос. Тут нам нужен Spring Context.

Ключевые аннотации:

🔴@SpringBootTest - Поднимает весь контекст приложения (тяжело и медленно, но честно).
🔴@WebMvcTest - Поднимает только веб-слой (Контроллеры). Сервисы и БД не грузятся.
🔴@MockBean - Главная магия Spring Test. Это как @Mock, только этот мок кладется прямо в Spring Context, заменяя собой настоящий бин.

💻 Пример теста Контроллера (@WebMvcTest):


@WebMvcTest(UserController.class) // Грузим только контроллер
class UserControllerTest {

@Autowired
private MockMvc mockMvc; // Инструмент для имитации HTTP-запросов

@MockBean // Заменяем настоящий сервис заглушкой в контексте Spring
private UserService userService;

@Test
void shouldReturnStatus200() throws Exception {
Mockito.when(userService.findById(1L)).thenReturn(new User("Alex"));

mockMvc.perform(get("/users/1")) // Делаем GET запрос
.andExpect(status().isOk()) // Ждем 200 OK
.andExpect(jsonPath("$.name").value("Alex")); // Проверяем JSON
}
}



⚔️ @Mock vs @MockBean - Не путать!

Это самый частый вопрос.

1. @Mock (из org.mockito): Используется в Unit-тестах. Быстро. Spring про него ничего не знает.
2. @MockBean (из spring-boot-test): Используется в Integration-тестах. Spring находит этот бин в контексте и подменяет его на мок. Медленнее.

🔥 Итог

🔴Пишите много Unit-тестов (они быстрые). Используйте Mockito.when(...).
🔴Пишите немного Integration-тестов (они проверяют связки). Используйте MockMvc.
🔴Никогда не тестируйте внешней API или боевую БД - мокайте их!

#SpringBoot #Testing #JUnit5 #Mockito #QualityAssurance

📲 Мы в MAX

👉@BookJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
📦 От Кода к Продакшену: JAR и Docker

В старые времена (Java EE) процесс деплоя был адом: нужно было установить на сервер Tomcat, настроить его, скомпилировать .war файл, закинуть его в папку... 🤯

Spring Boot принес концепцию Fat JAR (Жирный JAR).

🍔 1. Fat JAR - "Всё своё ношу с собой"

Spring Boot упаковывает ваше приложение, все библиотеки (зависимости) и даже сам веб-сервер (Tomcat) в один единственный файл .jar.

Этот файл работает как .exe в Windows. Ему ничего не нужно, кроме установленной Java.

Как собрать?
В терминале (в папке проекта):


# Для Maven
./mvnw clean package

# Для Gradle
./gradlew build



В папке target (или build/libs) появится файл myapp-0.0.1-SNAPSHOT.jar.

Как запустить?
Где угодно (на сервере, на ноутбуке друга), где есть Java:


java -jar myapp.0.0.1-SNAPSHOT.jar



Всё! Сервер стартует.



🐳 2. Docker - "Работает везде"

JAR это хорошо. Но что, если на сервере стоит Java 11, а у вас Java 17? Или другая ОС? Начинается проблема "На моем компьютере работало!".

Docker решает это, упаковывая ваше приложение вместе с Java и операционной системой в Контейнер.

Пишем Dockerfile
Создайте файл без расширения с именем Dockerfile в корне проекта.

Вариант для новичков (Простой):


# 1. Берем базовый образ с Java 17 (легковесный Alpine Linux)
FROM eclipse-temurin:17-jre-alpine

# 2. Копируем наш JAR внутрь образа
# (Предварительно нужно сделать mvn package руками!)
COPY target/*.jar app.jar

# 3. Говорим, какую команду запустить при старте контейнера
ENTRYPOINT ["java", "-jar", "/app.jar"]



Как запустить:


# 1. Собираем образ (Image)
docker build -t my-spring-app .

# 2. Запускаем контейнер
# -p 8080:8080 пробрасывает порт наружу
docker run -p 8080:8080 my-spring-app





🏗 3. Multi-stage Build (Уровень Pro)

В варианте выше вам нужно сначала собрать JAR руками. Это неудобно для CI/CD.
В профессиональном Dockerfile мы делаем сборку внутри Docker.


# --- Этап 1: Сборка (Build) ---
FROM maven:3.8.5-openjdk-17 AS builder
WORKDIR /app
COPY . .
# Собираем JAR, пропуская тесты (для скорости)
RUN mvn clean package -DskipTests

# --- Этап 2: Запуск (Run) ---
# Берем чистый, маленький образ для запуска
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# Копируем ТОЛЬКО готовый jar из первого этапа
COPY --from=builder /app/target/*.jar app.jar

ENTRYPOINT ["java", "-jar", "app.jar"]



Почему это круто? В финальном образе нет исходного кода и тяжелого Maven. Только Java и ваш JAR. Образ весит минимум, а собрать его можно одной командой docker build, даже если на компьютере вообще не установлена Java!

🔥 Итог

1. Maven/Gradle собирают код в один Fat JAR.
2. Dockerfile описывает среду для запуска.
3. Multi-stage build позволяет собирать и запускать приложение в изолированной среде, не засоряя систему.

#SpringBoot #Docker #DevOps #Deployment #Java

📲 Мы в MAX

👉@BookJava
👍62👎1
🧩 Микросервисы: Укрощение хаоса (Spring Cloud)

Когда у вас один сервис, всё просто. Но когда их становится 10, 20 или 50, возникают вопросы:

1. Как сервису А узнать IP-адрес сервиса Б? (А если он меняется динамически?)
2. Как не писать тонны кода для HTTP-запросов?
3. Как клиенту (фронтенду) обращаться ко всей этой куче сервисов?

Для этого есть три главных инструмента.

1️⃣ Eureka: Телефонная книга (Service Discovery)

В облаке сервисы постоянно перезапускаются, меняют IP-адреса и порты. Хардкодить https://localhost:8081 в коде - самоубийство.

Eureka Server - это реестр.

🔴Когда сервис (например, OrderService) запускается, он звонит в Eureka: "Привет, я OrderService, мой IP такой-то".

🔴Когда UserService хочет найти заказы, он спрашивает Eureka: "Где сейчас живет OrderService?".

В коде:
Вам нужно просто добавить аннотацию @EnableDiscoveryClient, и магия произойдет сама. Сервисы будут находить друг друга по имени, а не по IP.

2️⃣ OpenFeign: Магия общения

Окей, адрес мы нашли. Теперь нужно отправить запрос.
Раньше мы использовали RestTemplate - это было громоздко и некрасиво.
Feign позволяет вызывать удаленный REST-сервис так, будто это обычный метод интерфейса в вашем коде.

Было (RestTemplate):


String url = "https://order-service/orders/" + userId;
List<Order> orders = restTemplate.getForObject(url, List.class); // Фу, нетипизированно



Стало (FeignClient):
Вы просто пишете интерфейс, а реализацию Spring сгенерирует сам!


@FeignClient(name = "order-service") // Имя сервиса в Eureka
public interface OrderClient {

@GetMapping("/orders/{userId}")
List<Order> getUserOrders(@PathVariable Long userId);
}

// Использование в сервисе:
// List<Order> orders = orderClient.getUserOrders(123L);



Это называется Декларативный REST-клиент. Чисто, красиво, типизировано.

3️⃣ API Gateway: Единая точка входа

Представьте, что у вас 50 микросервисов. Фронтенд не должен знать адреса каждого из них (api.com:8081, api.com:8082...). Это небезопасно и сложно (CORS error, привет 👋).

Spring Cloud Gateway - это вахтер на входе.
Весь внешний мир стучится только в него (например, на порт 8080), а он уже сам разруливает, куда отправить запрос.

Что он делает:

1. Маршрутизация: /users/** -> лети в UserService, /orders/** -> лети в OrderService.

2. Безопасность: Проверяет JWT токен один раз на входе.

3. Rate Limiting: "Не больше 10 запросов в секунду от этого юзера".

Пример конфига (application.yaml):


spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://USER-SERVICE # lb = Load Balancer (через Eureka)
predicates:
- Path=/users/**



Итог: Как это работает вместе?

1. Сервисы просыпаются и регистрируются в Eureka.

2. Фронтенд шлет запрос на Gateway.

3. Gateway спрашивает у Eureka адрес нужного сервиса и пересылает запрос.

4. Если сервисам нужно пообщаться между собой, они используют Feign.

#Java #Microservices #SpringCloud #Eureka #Feign

📲 Мы в MAX

👉@BookJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🤝6👍5
📨 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 # Важно для масштабирования



2. Продюсер (Отправляем сообщение)
Нам нужен бин 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);
}
}



3. Консьюмер (Слушаем эфир)
Просто вешаем аннотацию @KafkaListener.


@Service
public class NotificationConsumer {

@KafkaListener(topics = "orders", groupId = "notification_group")
public void listen(String orderId) {
System.out.println("Получено событие для заказа: " + orderId);
// Тут логика отправки email/sms
sendEmail(orderId);
}
}



Kafka vs RabbitMQ (Коротко)

Частый холивар.

🔴RabbitMQ - это классическая Очередь. Сообщение прочитали -> оно удалилось. Это "умный брокер, глупые потребители".
🔴Kafka - это Лог. Сообщения хранятся на диске днями (или вечно). Их можно перечитывать заново (Replay). Это идеально для аналитики и восстановления данных.

🔥 Итог

Kafka позволяет микросервисам быть слабосвязанными (decoupled).

🔴Сервис А не знает о существовании Сервиса Б.
🔴Система выдерживает пиковые нагрузки (Kafka просто буферизирует сообщения, пока консьюмеры не разгребут их).

#Java #Kafka #Microservices #SpringCloud #Architecture

📲 Мы в MAX

👉@BookJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍82🔥2
🎥 Открытый урок «Eclipse Memory Analyzer (MAT): помощь в работе с heap».

🗓 04 февраля в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса
«Java Developer. Advanced».

Без MAT сложно найти причины OutOfMemoryError и утечек. Разберём базу и полезные приёмы анализа heap dump.


Что будет на вебинаре:
✔️ Ключевые концепции работы с MAT и навигация по heap dump.
✔️ Экспресс-анализ дампа памяти: где искать утечки и рост объектов.
✔️ Разбор реального дампа в онлайне на предмет memory leak.

В результате вебинара:
Вы сможете самостоятельно проводить анализ дампов памяти. Будете знать возможности MAT и какие задачи решаются с его помощью.

Кому будет интересно:
Подойдёт Java-разработчикам, инженерам сопровождения и SRE, кто сталкивается с OutOfMemoryError и падениями по памяти.

🔗 Ссылка на регистрацию: https://vk.cc/cU0oou

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
🚀 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>



2. Включаем рубильник
Над главным классом вешаем @EnableCaching.

💻 Магия аннотаций

У нас есть 3 главные аннотации, которые вы обязаны знать:

1. @Cacheable — "Запомни меня"

Вешаем над методами поиска (get, find).


@Service
public class UserService {

@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
// Имитация долгого запроса в БД (3 секунды)
simulateSlowService();
return userRepository.findById(id).orElseThrow();
}
}



Результат: Первый вызов займет 3 секунды. Второй, третий и тысячный вызов с тем же ID займут 0.001 секунды.

2. @CacheEvict - "Забудь меня"

Самая большая проблема кэширования - инвалидация. Если мы обновили данные юзера в БД, а в кэше осталась старая версия — это баг.
При обновлении или удалении мы должны очистить кэш.


@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}



Теперь при следующем вызове getUserById(id) Spring увидит, что кэш пуст, и снова сходит в БД за свежими данными.

3. @CachePut - "Обнови меня"

Используется реже. Метод выполняется всегда, а его результат кладется в кэш, обновляя старое значение.

🆚 Redis vs HashMap

"Зачем мне Redis, если я могу создать ConcurrentHashMap прямо в Java?"

1. Память: Если данных много, HashMap съест всю память JVM, и приложение упадет (OutOfMemory). Redis живет отдельно.

2. Микросервисы: Если у вас запущено 3 экземпляра сервиса, у каждого будет свой HashMap. Кэш будет рассинхронизирован. Redis это общий кэш для всех инстансов.

3. Живучесть: При перезагрузке приложения HashMap очищается. Redis (обычно) работает на отдельном сервере и хранит данные даже при рестарте вашего кода.

⚠️ Важный нюанс: Serializable

Чтобы положить Java-объект в Redis, его нужно превратить в байты (сериализовать).
Ваши DTO и Entity должны реализовывать интерфейс Serializable, либо (что правильнее) нужно настроить Jackson Serializer, чтобы хранить данные в Redis в читаемом JSON-формате.

🔥 Итог: кэширование - это самый простой способ масштабирования.

🔴Часто читаете, редко меняете? -> @Cacheable.
🔴Удалили/Обновили? -> @CacheEvict.

#Java #Spring #Redis #Caching #Performance

📲 Мы в MAX

👉@BookJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
👮‍♂️ 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();
}
}



🔓 Что такое OAuth2?

JWT - это когда вы сами выдаете токены.
OAuth2 - это когда токены выдает кто-то большой и доверенный (Google, Facebook, GitHub).

Кнопка "Войти через Google" - это OAuth2.

1. Вы перенаправляете юзера на Google.

2. Google спрашивает: "Разрешить приложению MyShop узнать ваш email?".

3. Google возвращает вам токен.

4. Вы верите этому токену, потому что доверяете Google.

В Spring Boot это настраивается буквально в 5 строк в application.yaml, но под капотом там огромная машина стандартов.


🔥 Итог: безопасность для бэкенда это:

1. Spring Security как движок.

2. JWT как пропуск.

3. Stateless режим (без сессий).

4. HTTPS (обязательно, иначе токен украдут).

#SpringSecurity #JWT #OAuth2 #Java #CyberSecurity

📲 Мы в MAX

👉@BookJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍92
А вы справитесь с тестом по HighLoad?

Как пройти путь от разработчика до архитектора высоконагруженных систем для работы с крупными проектами?

Пройдите тест, проверьте свои знания для обучения на курсе «Highload Architect» от OTUS. А так же и получите скидку 🎁 до 15.02.2026 - подробности у менеджера.

➡️ Пройти Тест https://vk.cc/cU6fLb

На курсе вы освоите проектирование масштабируемых и отказоустойчивых систем, оптимизацию производительности, работу с современными инструментами для создания высоконагруженных решений и лучшие практики разработки серверных приложений.

❗️Практическое обучение проводится в прямом эфире — вебинары не являются предзаписанными.

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
🏗 SOLID - Пять заповедей программиста

Почему один проект живет 10 лет и его легко дорабатывать, а другой через полгода превращается в "Legacy", к которому страшно подходить?
Разница в соблюдении принципов SOLID.

Это аббревиатура из 5 правил, сформулированных Робертом Мартином (Дядя Боб). Если вы нарушаете их - ваш код "гниет".

Давайте разберем каждую букву.

1️⃣ S - Single Responsibility Principle (Единственная ответственность)

"У класса должна быть только одна причина для изменения."


Как делают новички (God Object):
Класс OrderService делает всё:

1. Считает сумму заказа.
2. Сохраняет заказ в БД.
3. Отправляет email клиенту.
4. Генерирует PDF-чек.

Если бизнес попросит изменить формат чека — мы лезем в этот класс. Если поменяется логика БД - опять в него. Риск сломать отправку писем при правке базы данных огромен!

Как надо:
Разбиваем на маленькие классы:

OrderCalculator (считает).
OrderRepository (сохраняет).
EmailNotificationService (шлет письма).
PdfGenerator (печатает).

OrderService теперь просто дирижер, который вызывает эти компоненты.


2️⃣ O - Open-Closed Principle (Открытость/Закрытость)

"Программные сущности должны быть открыты для расширения, но закрыты для модификации."


Это значит: Не меняйте старый рабочий код, чтобы добавить новую фичу.

Плохо:
У нас есть метод расчета доставки.


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 { ... }




3️⃣ L - Liskov Substitution Principle (Принцип подстановки Барбары Лисков)

"Наследники должны без проблем заменять родителей."


Если у вас есть класс Bird с методом fly(), а вы создали наследника Penguin (Пингвин), который при вызове fly() бросает ошибку (потому что пингвины не летают) - вы нарушили LSP.

Суть: Если код работает с базовым классом, он должен работать и с любым его наследником, не зная об этом и не ломаясь.


4️⃣ I - Interface Segregation Principle (Разделение интерфейсов)

"Много маленьких интерфейсов лучше, чем один огромный."


Плохо:
Интерфейс Worker имеет методы work() и eat().
Мы создаем класс Robot. Роботы работают, но не едят.
Нам придется реализовать метод eat() и оставить его пустым или кинуть ошибку. Это мусор.

Хорошо:
Разбейте на Workable и Eatable.
Человек имплементирует оба. Робот - только Workable.


5️⃣ D - Dependency Inversion Principle (Инверсия зависимостей)

"Зависьте от абстракций, а не от конкретики."


Это то, что мы учили в Spring (DI).
Ваш Service не должен зависеть от PostgresRepository. Он должен зависеть от интерфейса Repository.
Тогда вы сможете легко подменить Postgres на MySQL или Mock-объект для тестов, не меняя ни строчки в Сервисе.


SOLID - это фильтр. Прежде чем закоммитить код, спросите себя:

1. Не делает ли мой класс слишком много? (S)
2. Придется ли мне переписывать этот класс, если добавятся новые условия? (O)
3. Не ломаю ли я поведение родителя? (L)
4. Не заставляю ли я других реализовывать ненужные методы? (I)
5. Завишу ли я от интерфейсов или от конкретных классов? (D)

#Architecture #SOLID #CleanCode #OODesign

📲 Мы в MAX

👉@BookJava
👍135🔥3
🏗 Порождающие паттерны: Как рождаются объекты?

Создать объект просто: 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;
}
}



⚠️ Важно: В Spring Boot все бины по умолчанию - синглтоны. Вам не нужно писать этот код руками, контейнер Spring сам следит, чтобы сервис был создан один раз.


2️⃣ Builder (Строитель)

Суть: Позволяет создавать сложные объекты пошагово. Спасает от «Телескопического конструктора» (когда у вас конструктор с 10 аргументами, и вы не помните, где там age, а где height).

Было (Ужас):
new User("Alex", null, true, "admin", 25, null);

Стало (Builder):


User user = User.builder()
.name("Alex")
.age(25)
.role("ADMIN")
.active(true)
.build();



🛠 Лайфхак:
В Java не нужно писать Билдер руками (это 50 строк кода). Просто поставьте аннотацию Lombok @Builder над классом.


3️⃣ Factory Method (Фабричный метод)

Суть: Определяет интерфейс для создания объекта, но оставляет подклассам решение о том, какой класс инстанцировать.
Это реализация принципа Open-Closed. Мы добавляем новые типы продуктов, не ломая существующий код.

Пример: У нас есть "Уведомления". Сегодня это Email, завтра SMS, послезавтра Push.


// 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!");



🔥 Итог

1. Singleton - когда нужен один объект на всю систему.
2. Builder - когда объект сложный и у него много параметров.
3. Factory - когда мы не знаем заранее, какой конкретно объект понадобится, или хотим скрыть логику выбора.

#DesignPatterns #GoF #Singleton #Builder #Factory #Java

📲 Мы в MAX

👉@BookJava
👍5🔥3
🔴 Завтра тестовое собеседование с Java-разработчиком

11 февраля(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.

Как это будет:
📂 Сергей Чамкин, старший разработчик из Uzum, ex-WildBerries, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Cергей будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Сергею

Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.

Переходи в нашего бота, чтобы получить ссылку на эфир →
@shortcut_sh_bot

Реклама.
О рекламодателе.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍31
🏗 Структурные паттерны: Адаптер, Декоратор, Прокси

Когда вы пытаетесь соединить старую библиотеку с новым кодом или добавить логирование, не переписывая половину проекта - вы используете эти паттерны.

🔌 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());
}
}




🎁 2. Decorator (Декоратор/Обертка)

Суть: Динамически добавляет объекту новые обязанности (функционал).
Это альтернатива наследованию. Вместо того чтобы создавать 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)).


🛡️ 3. Proxy (Заместитель)

Суть: Объект-прокладка, который контролирует доступ к другому объекту.
Клиент думает, что общается с реальным объектом, а на самом деле говорит с его заместителем.

Зачем?

1. Ленивая загрузка (Lazy Loading): Не грузить тяжелую картинку/БД, пока ее реально не попросят.

2. Безопасность: Проверить права доступа перед выполнением метода.

3. Логирование: Записать "Метод вызван" и передать вызов дальше.

Пример:


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();
}
}



Spring Magic: Весь Spring держится на Прокси! Когда вы ставите @Transactional, Spring создает прокси вокруг вашего сервиса, открывает транзакцию, вызывает ваш метод, а потом закрывает транзакцию.


🔥 Итог

🔴Adapter - меняет интерфейс объекта (чтобы подошел).

🔴Decorator - меняет поведение объекта (добавляет фичи), не меняя интерфейс.

🔴Proxy - контролирует доступ к объекту (ленивость, защита).

#DesignPatterns #Adapter #Decorator #Proxy #Architecture

📲 Мы в MAX

👉@BookJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
🎥 Открытый урок «Class Data Sharing и его перспективы».

🗓 17 февраля в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса
«Java Developer. Advanced».

Быстрый, лёгкий старт Java-сервисов — конкурентное преимущество. Разберём, чем поможет Class Data Sharing и где он уместен.


Что будет на вебинаре:
✔️ Назначение Class Data Sharing.
✔️ Поддержка и использование в Spring Boot.
✔️ Разница с Native Image для GraalVM.

В результате вебинара:
Сможете запустить Spring Boot-приложение с использованием CDS и понять базовую настройку. Получите представление, чем CDS отличается от Native Image (GraalVM).

Кому будет интересно:
Подойдёт Java-разработчикам и инженерам, которым важно ускорить старт сервисов и оптимизировать время запуска в проде.

🔗 Ссылка на регистрацию: https://vk.cc/cUiTYA

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
🧠 Поведенческие паттерны: Стратегия, Наблюдатель, Цепочка

Эти паттерны помогают избежать спагетти-кода, где один класс управляет всем миром через гигантские 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);
}
}




👀 2. Observer (Наблюдатель / Listener)

Суть: Один объект (Subject) меняет свое состояние, и все зависимые от него объекты (Observers) тут же узнают об этом.

Аналогия: YouTube-канал.

🔴Блогер (Subject) выпускает видео.
🔴Подписчики (Observers) получают уведомление.
🔴Если вы отписались - уведомления приходить перестанут.

Код: Это основа всех UI-фреймворков (кнопка нажата -> слушатель сработал) и даже архитектуры Kafka.


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); // Уведомляем всех!
}
}
}




🔗 3. Chain of Responsibility (Цепочка обязанностей)

Суть: Запрос передается по цепочке обработчиков. Каждый обработчик решает: обработать запрос самому или передать следующему.

Аналогия: Техподдержка.

1. Сначала отвечает Чат-бот (Уровень 1). Не справился? -> Передает дальше.

2. Оператор колл-центра (Уровень 2). Не справился? -> Передает дальше.

3. Инженер (Уровень 3). Решает проблему.

Пример в Spring:
Spring Security работает именно так! Ваш HTTP-запрос проходит через цепочку фильтров:

🔴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); // Передаем следующему
}
}
}




🔥 Итог

🔴Strategy - Выбираем алгоритм действия (Платим картой или кэшем?).
🔴Observer - Слушаем изменения (Вышло видео -> пришло уведомление).
🔴Chain of Responsibility - Передаем эстафету (Фильтр 1 -> Фильтр 2 -> Контроллер).

#DesignPatterns #Behavioral #Strategy #Observer #ChainOfResponsibility

📲 Мы в MAX

👉@BookJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
🧅 Архитектура: От Слоев к Луковице

Проблема Слоенки (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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥2
🧠 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);
}



Богатая (DDD):
Логика и валидация живут внутри Сущности. Сервис просто координирует работу.


// 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);
}



🔥 Итог

DDD - это сложно, но необходимо для больших проектов.

1. Говорите на языке бизнеса (Ubiquitous Language).
2. Разделяйте модели (Bounded Contexts).
3. Используйте Value Objects вместо примитивов.
4. Прячьте логику внутрь Rich Domain Model.

#Architecture #DDD #DomainDrivenDesign #Java #Microservices

📲 Мы в MAX

👉@BookJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍61👎1
❗️Микросервисы часто обещают масштабируемость, но на практике превращаются в сложную и хрупкую систему.

Причина почти всегда одна — неверная декомпозиция. Слишком мелкие сервисы, плотные связи, распределённые транзакции и постоянные проблемы с данными.

На открытом уроке:
- разберём, как правильно делить систему на сервисы - поговорим о границах микросервисов, ключевых шаблонах декомпозиции, подходах к взаимодействию и управлению согласованностью данных
- вы увидите, как принимать архитектурные решения не по шаблону, а с учётом бизнес-контекста, нагрузки и эволюции системы
- разберём типовые ошибки, из-за которых микросервисы теряют автономность и становятся сложнее монолита

📌 Встречаемся 18 февраля в преддверии старта курса «Highload Architect».

Зарегистрируйтесь, чтобы не пропустить: https://vk.cc/cUudMf

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1