Новинки в мире монолитов: как современные подходы оживляют классическую архитектуру (с примерами кода)

Статья о современных подходах к разработке монолитных приложений, включая модульную архитектуру, использование событий предметной области и принципы чистой архитектуры. Приведены практические примеры кода на Java/Spring Boot.
В эпоху тотального увлечения микросервисами слово "монолит" стало почти ругательным. Однако, к середине 2020-х годов пришло отрезвление. Сложность управления распределенными системами, проблемы с согласованностью данных, латентность и операционные накладные расходы заставили многих пересмотреть свои взгляды. На сцену вернулся монолит – но не тот громоздкий, спагетти-код из прошлого, а современный, модульный, хорошо структурированный. Новые подходы, фреймворки и практики доказывают, что монолитная архитектура может быть масштабируемой, поддерживаемой и быстрой в разработке для огромного класса проектов.

Современный монолит – это, прежде всего, модульный монолит (Modular Monolith). Ключевая идея: логически разделить приложение на высокосвязные модули с четкими границами внутри одной кодовой базы и процесса. Каждый модуль инкапсулирует свою предметную область, имеет свой интерфейс (API) для общения с другими модулями, а все взаимодействия строго регламентированы. Это предотвращает превращение кода в "большую шаровую грязь" (Big Ball of Mud).

Рассмотрим пример на языке Java с использованием Spring Boot и паттерна "Модуль". Мы создадим простое приложение для управления библиотекой с двумя модулями: `book-catalog` и `user-management`.

```java
// Модуль book-catalog
package com.library.catalog;

public interface BookService {
 Book findById(Long id);
}

@Service
public class BookServiceImpl implements BookService {
 private final BookRepository repository;
 // Инжектим только свои репозитории
 public BookServiceImpl(BookRepository repository) { this.repository = repository; }
 @Override
 public Book findById(Long id) { return repository.findById(id).orElseThrow(); }
}

// Класс Book и BookRepository находятся в том же пакете (модуле)
```

```java
// Модуль user-management
package com.library.user;

public interface UserService {
 User findById(Long id);
}

@Service
public class UserServiceImpl implements UserService {
 private final UserRepository repository;
 public UserServiceImpl(UserRepository repository) { this.repository = repository; }
 @Override
 public User findById(Long id) { return repository.findById(id).orElseThrow(); }
}
```

Теперь, чтобы модуль `user-management` мог выполнить операцию, требующую данных о книге (например, проверить, доступна ли книга для выдачи), мы не инжектим `BookService` напрямую. Вместо этого мы создаем четкий API-слой, часто через события (Domain Events) или внутренние вызовы, которые могут быть в будущем вынесены в отдельный сервис.

```java
// В модуле book-catalog объявляем событие предметной области
package com.library.catalog.events;

public class BookBorrowRequestedEvent {
 private final Long bookId;
 private final Long userId;
 // конструктор, геттеры
}

// В том же модуле слушатель, который обрабатывает бизнес-логику
@Service
@Transactional
public class BookBorrowEventHandler {
 private final BookRepository repository;
 public void handle(BookBorrowRequestedEvent event) {
 Book book = repository.findById(event.getBookId()).orElseThrow();
 if (!book.isAvailable()) {
 throw new BookNotAvailableException();
 }
 book.markAsBorrowed();
 repository.save(book);
 }
}

// В модуле user-management (в слое приложения) инициируем событие
package com.library.user.application;

@Service
@Transactional
public class BorrowBookService {
 private final ApplicationEventPublisher eventPublisher;
 private final UserRepository userRepository;

 public void borrowBook(Long userId, Long bookId) {
 // Проверяем пользователя
 userRepository.findById(userId).orElseThrow();
 // Публикуем событие в том же транзакционном контексте (в рамках монолита)
 eventPublisher.publishEvent(new BookBorrowRequestedEvent(bookId, userId));
 // Дальнейшая логика (создание записи о выдаче) может быть в своем обработчике
 }
}
```

Такой подход, использующий события, делает модули слабо связанными. Завтра мы можем вынести модуль `book-catalog` в отдельный микросервис, заменив `ApplicationEventPublisher` на отправку сообщений в брокер (Kafka, RabbitMQ), изменив лишь конфигурацию и способ доставки события, но не бизнес-логику внутри модулей.

Еще одна мощная новинка – это использование компоновщиков зависимостей и версионирования модулей. Инструменты вроде Java Platform Module System (JPMS) или просто строгое следование принципам чистой архитектуры и гексагональной архитектуры (Ports & Adapters) позволяют изолировать ядро приложения от инфраструктуры (БД, HTTP-контроллеры, UI).

Пример адаптера для базы данных в модуле `book-catalog`:

```java
// Порт (интерфейс в ядре модуля)
package com.library.catalog.core.port.out;

public interface BookRepositoryPort {
 Book save(Book book);
 Optional findById(Long id);
}

// Адаптер (в инфраструктурном слое модуля)
package com.library.catalog.infrastructure.persistence.jpa;

@Repository
public class BookJpaAdapter implements BookRepositoryPort {
 private final BookJpaRepository jpaRepository; // Spring Data JPA интерфейс
 // ... маппинг между Book (доменная сущность) и BookEntity (JPA сущность)
 @Override
 public Book save(Book book) {
 BookEntity entity = mapToEntity(book);
 BookEntity saved = jpaRepository.save(entity);
 return mapToDomain(saved);
 }
}
```

Такой подход позволяет с легкостью заменить JPA на, например, MongoDB или простой in-memory кэш для тестов, не трогая ядро модуля.

Современные фреймворки, такие как Spring Boot, Micronaut или Quarkus, идеально подходят для построения таких модульных монолитов. Они обеспечивают быстрое стартовое время, развитую систему dependency injection и отличную поддержку событийной модели. А когда придет время масштабироваться, вы сможете "вырезать" наиболее нагруженный модуль и запустить его как отдельный микросервис, сохранив большую часть кода.

Таким образом, новинка в мире монолитов – это не новый фреймворк, а новый подход к организации кода. Это дисциплина, которая позволяет получить преимущества единой кодовой базы, простоты развертывания и сильной согласованности данных, при этом закладывая фундамент для возможного эволюционного расщепления в будущем. Монолит перестал быть тупиковой архитектурой, он стал разумной отправной точкой и, во многих случаях, оптимальным конечным решением.
495 5

Комментарии (9)

avatar
ypwcjg 01.04.2026
А как насчёт масштабирования? Современный монолит с этим справится?
avatar
k1c8ydiflg 02.04.2026
. Дисциплина нужна.
avatar
29yrvwz 02.04.2026
Наконец-то здравый взгляд на архитектуру! Микросервисы — не панацея.
avatar
tcqo2s21 02.04.2026
Главное — не скатиться в тот самый
avatar
oclu28 02.04.2026
Интересно, а как в таком монолите организовать независимые деплои команд?
avatar
0ztjkfn 02.04.2026
Всё возвращается на круги своя. Но теперь с лучшими практиками.
avatar
7yxf7ls3lkd5 03.04.2026
Примеры кода были бы очень кстати. Особенно про модульность.
avatar
ltzmeraqg 03.04.2026
Хорошая статья. Напомнила, что нужно выбирать архитектуру под задачу, а не моду.
avatar
5hs91d 03.04.2026
Согласен. Сложность микросервисов часто не оправдана для средних проектов.
Вы просмотрели все комментарии