Перейти к основному содержимому

Рекомендации по разработке на Java

Разработчику Архитектору

Рекомендации по разработке на Java

1. Требования по именованию

1.1. Общие правила именования

Имена в Java должны точно отражать назначение сущности без избыточных уточнений. Каждый элемент языка использует соответствующую нотацию:

Элемент языкаНотацияПример
Пакетlowercasecom.example.service
Класс, запись (record)PascalCaseUserService
ИнтерфейсPascalCaseDataRepository
Перечисление (тип)PascalCaseOrderStatus
Перечисление (значение)UPPER_SNAKE_CASEPENDING, COMPLETED
Константа (static final)UPPER_SNAKE_CASEMAX_RETRY_COUNT
Статическое полеcamelCaseinstanceCounter
Приватное полеcamelCaseuserRepository
Локальная переменнаяcamelCasetotalAmount
МетодcamelCasecalculateTotal
Параметр методаcamelCaseorderId
Типовой параметрОдна заглавная букваT, E, K, V

Имена пакетов всегда пишутся строчными буквами без подчёркиваний. Используйте обратный доменный порядок для глобальной уникальности: ru.spirzen.project.module.


1.2. Именование классов и интерфейсов

Классы именуются существительными или словосочетаниями с существительным в конце. Интерфейсы также именуются существительными, избегая префиксов вроде I или Interface:

// Хорошо
public class OrderProcessor { }
public interface PaymentGateway { }

// Плохо
public class OrderProcessingService { } // избыточное слово "Service"
public interface IPaymentProvider { } // префикс I

Для классов-исключений используйте суффикс Exception:

public class ValidationException extends RuntimeException { }
public class ResourceNotFoundException extends Exception { }

1.3. Именование методов

Методы именуются глаголами или глагольными словосочетаниями. Для методов-предикатов используйте префиксы is, has, can, should:

public boolean isActive() { }
public boolean hasPermission(String resource) { }
public void submitOrder(Order order) { }
public Order findOrderById(Long id) { }

Методы, возвращающие новый объект на основе текущего, именуются с префиксом to или as:

public String toString() { }
public BigDecimal toBigDecimal() { }

Методы, изменяющие состояние объекта и возвращающие самого себя для цепочек вызовов, именуются глаголами без специальных префиксов:

public OrderBuilder withCustomer(Customer customer) { }

1.4. Именование переменных и параметров

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

Код ITЗагрузка примера кода…

Для коллекций используйте множественное число или суффиксы List, Set, Map при необходимости уточнения типа:

List<Order> orders = orderRepository.findAll();
Map<Long, User> userMap = users.stream()
.collect(Collectors.toMap(User::getId, Function.identity()));

2. Требования по оформлению кода

2.1. Отступы и пробелы

Используйте 4 пробела для отступов. Не применяйте символы табуляции. Между операторами и операндами ставьте один пробел:

// Хорошо
int sum = a + b;
if (value > threshold) {
process(value);
}

// Плохо
int sum=a+b;
if(value>threshold){
process(value);
}

Не ставьте пробелы:

  • После открывающей круглой скобки и перед закрывающей
  • Перед запятой и точкой с запятой
  • После открывающей угловой скобки типового параметра и перед закрывающей
// Хорошо
List<String> names = new ArrayList<>();
method(param1, param2);
if (condition) { }

// Плохо
List< String > names = new ArrayList< >();
method( param1 , param2 );
if ( condition ) { }

2.2. Фигурные скобки и стиль Олмана

Всегда используйте фигурные скобки даже для однострочных блоков. Применяйте стиль Олмана: открывающая скобка на новой строке с тем же отступом, что и управляющая конструкция:

Код ITЗагрузка примера кода…


2.3. Длина строк и переносы

Максимальная длина строки — 120 символов. При переносе аргументов метода или элементов цепочки вызовов каждый элемент размещайте на новой строке с дополнительным отступом:

// Хорошо
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException(userId));

Order order = new OrderBuilder()
.withCustomerId(customerId)
.withItems(items)
.withShippingAddress(address)
.build();

// Плохо
User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException(userId));

Для длинных условий в if переносите каждую часть условия на новую строку с выравниванием операторов:

// Хорошо
if (order.getStatus() == OrderStatus.PENDING
&& order.getAmount().compareTo(BigDecimal.ZERO) > 0
&& order.getCustomer().isActive()) {
processOrder(order);
}

2.4. Пустые строки

Разделяйте логические блоки кода одной пустой строкой:

  • Между методами класса
  • Между полями и конструктором
  • Между различными секциями внутри метода (объявление переменных, основная логика, возврат результата)
  • Перед и после блоков try-catch
  • Между импортируемыми пакетами разных организаций

Не оставляйте несколько пустых строк подряд и не ставьте пустые строки в начале или конце блока кода.


3. Структура проекта и организация файлов

3.1. Стандартная структура каталогов Maven/Gradle

src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ ├── application/ # Слой приложения (сервисы, команды)
│ │ ├── domain/ # Доменная модель (сущности, агрегаты)
│ │ ├── infrastructure/ # Инфраструктурные компоненты (БД, внешние сервисы)
│ │ └── interfaces/ # Внешние интерфейсы (контроллеры, адаптеры)
│ └── resources/
│ ├── application.properties
│ └── db/migration/ # Миграции базы данных
└── test/
├── java/
│ └── com/
│ └── example/
│ ├── application/
│ ├── domain/
│ └── infrastructure/
└── resources/
└── application-test.properties

3.2. Организация классов внутри пакетов

Внутри каждого функционального пакета группируйте классы по назначению:

domain/
├── model/ # Сущности и агрегаты
├── valueobject/ # Объекты-значения
├── repository/ # Интерфейсы репозиториев
└── event/ # События домена

Избегайте создания пакетов с именами вроде utils, helpers, common. Такие пакеты становятся сборниками несвязанной функциональности. Вместо этого размещайте утилитарные классы в пакетах, соответствующих их доменной области.


3.3. Файлы и их содержимое

Каждый файл .java должен содержать только один публичный класс с именем, совпадающим с именем файла. Допускается размещение нескольких непубличных классов в одном файле, если они тесно связаны и используются только внутри основного класса.

Располагайте элементы класса в следующем порядке:

  1. Константы (public static final)
  2. Статические поля
  3. Поля экземпляра
  4. Конструкторы
  5. Методы жизненного цикла (например, @PostConstruct)
  6. Публичные методы
  7. Защищённые методы
  8. Пакетные методы
  9. Приватные методы
  10. Вложенные классы и интерфейсы

4. Проектирование классов и интерфейсов

4.1. Принцип единственной ответственности

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

Код ITЗагрузка примера кода…


4.2. Инкапсуляция и сокрытие реализации

Поля класса должны быть приватными. Доступ к состоянию объекта предоставляется через методы. Избегайте создания публичных полей и избыточных геттеров/сеттеров:

Код ITЗагрузка примера кода…


4.3. Неизменяемость

Предпочитайте неизменяемые классы там, где это возможно. Неизменяемые объекты проще в использовании, потокобезопасны и защищены от побочных эффектов:

Код ITЗагрузка примера кода…

Для создания неизменяемых классов:

  • Объявляйте класс как final
  • Делайте все поля private final
  • Не предоставляйте сеттеры
  • Защищайте изменяемые компоненты при передаче (копируйте коллекции)
  • Используйте records в современных версиях Java для простых неизменяемых структур данных

4.4. Интерфейсы и абстракции

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

Код ITЗагрузка примера кода…

Предпочитайте зависимости от интерфейсов, а не от конкретных классов. Это упрощает тестирование и замену реализаций.


5. Проектирование методов и функций

5.1. Длина и сложность методов

Метод должен умещаться на один экран без прокрутки (рекомендуемая длина — до 20 строк). Методы с высокой цикломатической сложностью (>10) следует декомпозировать:

Код ITЗагрузка примера кода…


5.2. Количество параметров

Метод должен принимать не более 3-4 параметров. При необходимости передачи большего количества данных используйте объекты-параметры или строители:

Код ITЗагрузка примера кода…


5.3. Побочные эффекты

Методы должны иметь предсказуемое поведение. Методы с побочными эффектами должны быть чётко идентифицируемы по имени:

Код ITЗагрузка примера кода…


6. Работа с исключениями

6.1. Использование исключений по назначению

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

Код ITЗагрузка примера кода…


6.2. Типы исключений

Предпочитайте проверяемые исключения (checked exceptions) для ситуаций, которые вызывающий код может и должен обработать. Используйте непроверяемые исключения (unchecked exceptions) для программных ошибок и условий, которые вызывающий код не может разумно обработать:

Код ITЗагрузка примера кода…

Создавайте специализированные типы исключений вместо использования общих классов вроде Exception или RuntimeException. Это улучшает читаемость кода и позволяет точно обрабатывать различные ошибочные ситуации.


6.3. Обработка исключений

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

// Плохо: пустой catch-блок
try {
sendNotification(user);
} catch (NotificationException e) {
// ничего не делаем
}

// Хорошо: логирование исключения
try {
sendNotification(user);
} catch (NotificationException e) {
logger.warn("Failed to send notification to user {}", user.getId(), e);
}

Избегайте перехвата общих исключений (Exception, Throwable) на уровне отдельных методов. Перехватывайте только те типы исключений, которые вы можете корректно обработать. Общие исключения следует перехватывать только на самых верхних уровнях приложения (контроллеры, точки входа).

При логировании исключений всегда используйте метод, который записывает полный стек вызовов:

// Хорошо
logger.error("Processing failed for order {}", orderId, exception);

// Плохо: теряется стек вызовов
logger.error("Processing failed for order {}: {}", orderId, exception.getMessage());

6.4. Освобождение ресурсов

Всегда освобождайте ресурсы в блоке finally или используйте конструкцию try-with-resources для автоматического закрытия:

Код ITЗагрузка примера кода…


7. Работа с коллекциями и потоками

7.1. Выбор подходящего типа коллекции

Используйте наиболее подходящий тип коллекции для конкретной задачи:

  • ArrayList — когда важна производительность доступа по индексу и порядок элементов
  • LinkedList — когда важны частые вставки/удаления в начало или середину списка
  • HashSet — когда важна скорость проверки наличия элемента и порядок не имеет значения
  • LinkedHashSet — когда важна скорость проверки наличия и нужно сохранить порядок вставки
  • TreeSet — когда элементы должны быть отсортированы
  • HashMap — стандартный выбор для ассоциативных массивов
  • LinkedHashMap — когда важен порядок вставки ключей
  • TreeMap — когда ключи должны быть отсортированы

Возвращайте из методов интерфейсы коллекций, а не конкретные реализации:

// Хорошо
public List<User> findActiveUsers() {
return new ArrayList<>(activeUsers);
}

// Плохо
public ArrayList<User> findActiveUsers() {
return new ArrayList<>(activeUsers);
}

7.2. Потоки (Streams API)

Используйте Streams API для обработки коллекций, когда это улучшает читаемость кода. Избегайте чрезмерно сложных цепочек операций:

Код ITЗагрузка примера кода…

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


7.3. Избегание модификации коллекций во время итерации

Никогда не модифицируйте коллекцию напрямую во время итерации по ней с помощью for-each:

Код ITЗагрузка примера кода…


8. Асинхронное программирование

8.1. Выбор подходящей модели

Для асинхронных операций в Java используйте:

  • CompletableFuture — для композиции асинхронных операций
  • ExecutorService — для управления пулом потоков
  • Реактивные библиотеки (Project Reactor, RxJava) — для систем с высокой нагрузкой и потоковой обработкой данных
// Пример использования CompletableFuture
public CompletableFuture<Order> processOrderAsync(OrderRequest request) {
return CompletableFuture.supplyAsync(() -> validateOrder(request), executor)
.thenCompose(validatedOrder -> reserveInventoryAsync(validatedOrder))
.thenCompose(orderWithInventory -> chargePaymentAsync(orderWithInventory))
.thenApply(orderWithPayment -> finalizeOrder(orderWithPayment))
.exceptionally(ex -> handleOrderProcessingFailure(request, ex));
}

8.2. Обработка ошибок в асинхронном коде

Всегда обрабатывайте ошибки в асинхронных цепочках. Не оставляйте CompletableFuture без обработки исключений:

Код ITЗагрузка примера кода…


8.3. Управление потоками

Всегда используйте управляемые пулы потоков вместо создания потоков напрямую. Настройте размер пула в соответствии с характеристиками задач (CPU-bound или I/O-bound):

// Для CPU-bound задач: количество потоков ≈ количество ядер
int parallelism = Runtime.getRuntime().availableProcessors();
ExecutorService cpuBoundExecutor = Executors.newFixedThreadPool(parallelism);

// Для I/O-bound задач: больше потоков, так как они часто ждут
ExecutorService ioBoundExecutor = Executors.newFixedThreadPool(parallelism * 2);

Всегда корректно завершайте исполнители при остановке приложения:

// Graceful shutdown
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}

9. Комментарии и документация

9.1. Комментарии должны объяснять "почему", а не "что"

Комментарии предназначены для объяснения причин принятия решений, а не для описания того, что делает код. Хорошо написанный код сам по себе объясняет, что происходит. Комментарии нужны для контекста:

// Плохо: комментарий описывает очевидное
// Увеличиваем счётчик на 1
counter++;

// Хорошо: комментарий объясняет причину
// Счётчик увеличивается здесь, а не в конце цикла,
// чтобы избежать пропуска первого элемента при условии
counter++;

9.2. Javadoc для публичного API

Все публичные классы, методы и поля должны иметь документацию в формате Javadoc. Это особенно важно для библиотек и модулей, которые используются другими разработчиками:

Код ITЗагрузка примера кода…


9.3. Комментарии для сложной логики

Когда алгоритм или бизнес-логика сложны, добавляйте комментарии, объясняющие шаги:

Код ITЗагрузка примера кода…


9.4. TODO-комментарии

Используйте комментарии TODO для временных решений или известных проблем, но не оставляйте их надолго:

// TODO: Заменить на кэширование после реализации механизма инвалидации
List<Product> products = productRepository.findAll();

// TODO: Удалить после миграции на новую версию API (deadline: 2026-06-30)
@Deprecated
public void oldMethod() { }

9.5. Избегайте закомментированного кода

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

// Плохо: закомментированный код
// if (user.isAdmin()) {
// showAdminPanel();
// }

// Хорошо: удалённый код
// Админ-панель показывается только после успешной аутентификации
showAdminPanel();

10. Тестирование

10.1. Структура тестов

Тесты должны следовать соглашению об именовании и структуре:

Код ITЗагрузка примера кода…


10.2. Принцип AAA (Arrange-Act-Assert)

Каждый тест должен быть разделён на три части:

  • Arrange — подготовка данных и зависимостей
  • Act — выполнение тестируемого метода
  • Assert — проверка результатов

Код ITЗагрузка примера кода…


10.3. Тестирование граничных значений

Всегда тестируйте граничные значения и крайние случаи:

Код ITЗагрузка примера кода…


10.4. Mock-объекты и зависимости

Используйте фреймворки для создания моков (Mockito, EasyMock) для изолированного тестирования:

Код ITЗагрузка примера кода…


11. Производительность и оптимизация

11.1. Избегайте преждевременной оптимизации

Пишите чистый и понятный код в первую очередь. Оптимизируйте только после профилирования и выявления реальных узких мест:

// Плохо: преждевременная оптимизация
public List<User> findActiveUsers() {
List<User> result = new ArrayList<>(userRepository.countActive());
for (int i = 0; i < userRepository.countActive(); i++) {
result.add(userRepository.findActive().get(i));
}
return result;
}

// Хорошо: читаемый код, оптимизация при необходимости
public List<User> findActiveUsers() {
return userRepository.findActive();
}

11.2. Эффективная работа со строками

Используйте StringBuilder для конкатенации строк в циклах:

Код ITЗагрузка примера кода…


11.3. Кэширование результатов

Кэшируйте результаты дорогих операций, когда это уместно:

Код ITЗагрузка примера кода…


11.4. Ленивая инициализация

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

Код ITЗагрузка примера кода…


12. Безопасность

12.1. Валидация входных данных

Всегда проверяйте входные данные на стороне сервера, даже если валидация есть на клиенте:

Код ITЗагрузка примера кода…


12.2. Защита от SQL-инъекций

Всегда используйте параметризованные запросы:

Код ITЗагрузка примера кода…


12.3. Хеширование паролей

Никогда не храните пароли в открытом виде:

Код ITЗагрузка примера кода…


12.4. Защита от XSS

Экранируйте пользовательский ввод при выводе в HTML:

Код ITЗагрузка примера кода…


13. Логирование

13.1. Уровни логирования

Используйте правильные уровни логирования для разных типов сообщений:

Код ITЗагрузка примера кода…


13.2. Структурированное логирование

Используйте структурированный формат логов для упрощения анализа:

Код ITЗагрузка примера кода…


14. Обработка дат и времени

14.1. Использование java.time

Всегда используйте классы из пакета java.time вместо устаревших Date и Calendar:

Код ITЗагрузка примера кода…


14.2. Работа с часовыми поясами

Явно указывайте часовые пояса при работе с датами:

Код ITЗагрузка примера кода…


15. Работа с файлами и ресурсами

15.1. Потоковая обработка больших файлов

Используйте потоковую обработку для больших файлов, чтобы избежать переполнения памяти:

Код ITЗагрузка примера кода…


15.2. Работа с ресурсами классов

Используйте ClassLoader для доступа к ресурсам:

Код ITЗагрузка примера кода…


16. Многопоточность и конкурентность

16.1. Синхронизация и блокировки

Используйте современные механизмы синхронизации вместо ключевого слова synchronized:

Код ITЗагрузка примера кода…


16.2. Потокобезопасные коллекции

Используйте потокобезопасные коллекции из пакета java.util.concurrent:

Код ITЗагрузка примера кода…


16.3. CompletableFuture для асинхронных операций

Используйте CompletableFuture для композиции асинхронных операций:

Код ITЗагрузка примера кода…


17. Ошибки и анти-паттерны

17.1. Избегайте слишком больших классов

Классы должны быть компактными и сфокусированными на одной ответственности:

// Плохо: класс делает слишком много
public class UserService {
// 2000+ строк кода
// Работа с пользователями, уведомлениями, отчётами, интеграциями...
}

// Хорошо: разделение ответственности
public class UserService { /* только работа с пользователями */ }
public class NotificationService { /* только уведомления */ }
public class ReportService { /* только отчёты */ }

17.2. Избегайте глубокой вложенности

Глубокая вложенность усложняет чтение кода:

Код ITЗагрузка примера кода…


17.3. Избегайте магических чисел и строк

Выносите константы в отдельные поля:

Код ITЗагрузка примера кода…


17.4. Избегайте длинных методов

Разбивайте длинные методы на более мелкие:

Код ITЗагрузка примера кода…


18. Архитектурные подходы и паттерны проектирования

18.1. Слоистая архитектура

Слоистая архитектура разделяет приложение на горизонтальные слои с чёткими границами ответственности. Каждый слой зависит только от слоя ниже:

┌─────────────────────────────────────────┐
│ Презентационный слой (Presentation) │ ← HTTP-контроллеры, DTO
├─────────────────────────────────────────┤
│ Слой приложения (Application) │ ← Сервисы, команды, сценарии
├─────────────────────────────────────────┤
│ Доменный слой (Domain) │ ← Сущности, агрегаты, события
├─────────────────────────────────────────┤
│ Инфраструктурный слой (Infrastructure) │ ← Репозитории, внешние сервисы
└─────────────────────────────────────────┘

Презентационный слой преобразует входящие запросы в команды приложения и результаты в ответы клиенту. Слой приложения координирует выполнение бизнес-сценариев, используя доменные объекты. Доменный слой содержит чистую бизнес-логику без зависимостей от фреймворков. Инфраструктурный слой реализует технические детали — работу с базой данных, внешними API, очередями сообщений.


18.2. Чистая архитектура и зависимости

Чистая архитектура организует код вокруг бизнес-логики, а не вокруг фреймворков. Зависимости направлены внутрь к домену:

┌──────────────────┐
│ Сущности │ ← Чистые объекты бизнес-логики
└────────┬─────────┘

┌────────▼─────────┐
│ Сценарии │ ← Используют сущности
└────────┬─────────┘

┌────────▼─────────┐
│ Интерфейсы │ ← Адаптеры, контроллеры
└────────┬─────────┘

┌────────▼─────────┐
│ Фреймворки │ ← Spring, JPA, внешние библиотеки
└──────────────────┘

Доменные объекты не содержат аннотаций фреймворков. Аннотации @Entity, @Table размещаются в инфраструктурных классах-обёртках или в отдельных классах отображения (DTO/DAO). Такой подход позволяет легко заменять фреймворки и тестировать бизнес-логику без запуска контейнера.


18.3. Агрегаты и границы транзакций

Агрегат представляет собой группу связанных объектов, которые обрабатываются как единое целое. Корень агрегата обеспечивает целостность всех объектов внутри границы:

Код ITЗагрузка примера кода…

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


18.4. Событийно-ориентированная архитектура

События фиксируют факты произошедших изменений в системе. Доменные события генерируются агрегатами и обрабатываются другими компонентами:

Код ITЗагрузка примера кода…

События позволяют декомпозировать систему на слабо связанные компоненты. Изменения в одном модуле автоматически распространяются на другие без прямых зависимостей.


19. Работа с базами данных

19.1. Репозитории и спецификации

Репозиторий предоставляет абстракцию над хранилищем данных. Интерфейс репозитория размещается в доменном слое, реализация — в инфраструктурном:

Код ITЗагрузка примера кода…

Паттерн "Спецификация" позволяет создавать повторно используемые условия запросов:

Код ITЗагрузка примера кода…


19.2. Отложенные загрузки и графы выборки

Явно управляйте загрузкой связанных сущностей для предотвращения проблемы N+1:

// Плохо: неявная загрузка в цикле
List<Order> orders = orderRepository.findAll();
for (Order order : orders) {
// Каждый вызов getItems() выполняет отдельный SQL-запрос
List<OrderItem> items = order.getItems();
}

// Хорошо: явная загрузка графа
@EntityGraph(attributePaths = {"items", "customer"})
List<Order> orders = orderRepository.findAllWithItemsAndCustomer();

Определяйте несколько методов репозитория для разных сценариев использования с разными графами загрузки:

public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
@EntityGraph(attributePaths = {"items"})
Optional<OrderEntity> findByIdWithItems(Long id);

@EntityGraph(attributePaths = {"customer", "shippingAddress"})
Optional<OrderEntity> findByIdWithCustomerDetails(Long id);

@EntityGraph(attributePaths = {"items", "customer", "payments"})
Optional<OrderEntity> findByIdWithFullDetails(Long id);
}

19.3. Миграции базы данных

Используйте инструменты миграции баз данных для управления схемой:

Код ITЗагрузка примера кода…

Каждая миграция имеет уникальный номер версии и описательное имя. Миграции применяются последовательно и являются неизменяемыми после коммита в репозиторий.


20. Инъекция зависимостей и конфигурация

20.1. Конструкторная инъекция

Предпочитайте конструкторную инъекцию зависимостей. Она делает зависимости явными и упрощает тестирование:

Код ITЗагрузка примера кода…

Конструкторная инъекция гарантирует, что объект создаётся в полностью инициализированном состоянии. Все обязательные зависимости передаются через конструктор, опциональные — через сеттеры.


20.2. Конфигурационные свойства

Группируйте конфигурационные параметры в типизированные классы:

Код ITЗагрузка примера кода…

Типизированные свойства обеспечивают безопасность на этапе компиляции и упрощают навигацию по конфигурации в IDE.


20.3. Профили окружений

Используйте профили для разделения конфигурации разных окружений:

src/main/resources/
├── application.properties # общая конфигурация
├── application-dev.properties # разработка
├── application-test.properties # тестирование
├── application-stage.properties # предпродакшен
└── application-prod.properties # продакшен

Активация профиля происходит через переменную окружения или аргумент запуска:

# Активация профиля dev
java -jar application.jar --spring.profiles.active=dev

# Переменная окружения
export SPRING_PROFILES_ACTIVE=prod
java -jar application.jar

Конфигурация для продакшена никогда не должна содержать учётные данные в открытом виде. Используйте секреты через переменные окружения или менеджеры секретов.


21. Версионирование API

21.1. Стратегии версионирования

Выберите одну стратегию версионирования и применяйте её последовательно:

  • Версия в пути URL: /api/v1/orders, /api/v2/orders
  • Версия в заголовке: Accept: application/vnd.company.v2+json
  • Версия в параметре запроса: /api/orders?version=2 (не рекомендуется)

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

Код ITЗагрузка примера кода…


21.2. Поддержка нескольких версий

Поддерживайте несколько версий API параллельно в течение определённого периода миграции. Удаляйте старые версии только после уведомления клиентов и завершения миграции.

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/api/v1/**")
.addResourceLocations("classpath:/api/v1/");
registry.addResourceHandler("/api/v2/**")
.addResourceLocations("classpath:/api/v2/");
}
}

В документации каждой версии API указывайте дату прекращения поддержки и рекомендации по миграции на новую версию.


22. Валидация данных

22.1. Аннотации валидации Jakarta Bean Validation

Используйте стандартные аннотации для декларативной валидации:

Код ITЗагрузка примера кода…


22.2. Кастомные валидаторы

Создавайте кастомные аннотации для сложных правил валидации:

Код ITЗагрузка примера кода…


23. Мониторинг и метрики

23.1. Сбор метрик приложения

Интегрируйте Micrometer для сбора метрик:

Код ITЗагрузка примера кода…

Метрики экспортируются в системы мониторинга — Prometheus, Graphite, Datadog. Настройте алерты на критические показатели — ошибки, задержки, использование ресурсов.


23.2. Распределённое трассирование

Добавьте поддержку распределённого трассирования для микросервисных архитектур:

Код ITЗагрузка примера кода…

Каждый запрос получает уникальный trace ID, который передаётся между сервисами через заголовки HTTP. Это позволяет отслеживать полный путь запроса через систему.


24. Локализация и интернационализация

24.1. Файлы свойств для разных языков

Организуйте файлы локализации по языковым тегам:

src/main/resources/
├── messages.properties # fallback
├── messages_en.properties # английский
├── messages_ru.properties # русский
├── messages_fr.properties # французский
└── validation-messages_ru.properties

Содержимое файла messages_ru.properties:

order.created=Заказ #{0} успешно создан
order.not.found=Заказ с идентификатором {0} не найден
payment.successful=Платёж на сумму {0} успешно обработан
payment.failed=Ошибка обработки платежа: {0}

24.2. Использование MessageLookup

Инжектируйте MessageSource для получения локализованных сообщений:

Код ITЗагрузка примера кода…

Определяйте локаль пользователя на основе заголовка Accept-Language или сохранённых предпочтений в профиле.


25. Практики развёртывания и эксплуатации

25.1. Health checks

Реализуйте эндпоинты проверки работоспособности:

Код ITЗагрузка примера кода…

Health checks используются оркестраторами (Kubernetes, Docker Swarm) для определения готовности пода принимать трафик и его живости.


25.2. Graceful shutdown

Реализуйте корректное завершение работы приложения:

Код ITЗагрузка примера кода…

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


26. Работа с датами рождения и персональными данными

26.1. Хранение дат рождения

Храните дату рождения как LocalDate без часового пояса. Возраст рассчитывайте динамически:

Код ITЗагрузка примера кода…

Никогда не храните возраст как отдельное поле — он устаревает каждый день. Всегда рассчитывайте его на основе даты рождения.


26.2. Защита персональных данных

Применяйте принцип минимальных привилегий к персональным данным:

Код ITЗагрузка примера кода…

Шифруйте чувствительные данные на уровне базы данных или приложения. Хешируйте данные, используемые только для поиска (номера телефонов). Ограничивайте доступ к персональным данным через права ролей и аудит операций.


27. Инструменты и автоматизация

27.1. Форматирование кода с Spotless

Настройте Spotless для автоматического форматирования:

Код ITЗагрузка примера кода…

Добавьте задачу форматирования в процесс сборки:

check.dependsOn spotlessCheck
spotlessApply.dependsOn compileJava

Разработчики запускают ./gradlew spotlessApply перед коммитом. Внедрите проверку форматирования в пайплайн CI — сборка падает при несоответствии стилю.


27.2. Статический анализ с SonarQube

Интегрируйте SonarQube для анализа качества кода:

Код ITЗагрузка примера кода…

Настройте Quality Gates в SonarQube — максимальное количество багов, уязвимостей, code smells. Сборка ветки main должна проходить Quality Gate с оценкой A.


27.3. Генерация документации API

Используйте SpringDoc OpenAPI для автоматической генерации спецификации:

Код ITЗагрузка примера кода…

Документация доступна по эндпоинту /v3/api-docs в формате OpenAPI 3.0 и через веб-интерфейс Swagger UI по пути /swagger-ui.html.


Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.

Содержание