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

«Какой подход вы используете для разделения монолитного приложения на модули?» — вопрос из категории Архитектура, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Подход основан на принципах Domain-Driven Design (DDD) и постепенном выделении сервисов с сохранением работоспособности системы.

Основные шаги:

  1. Анализ и декомпозиция:
    • Строим граф зависимостей модулей.
    • Выявляем ограниченные контексты (Bounded Contexts) — логические домены с высокой связностью внутри и низкой снаружи.
  2. Определение границ модуля:
    • Группируем тесно связанные сущности в агрегаты.
    • Определяем публичный API модуля (интерфейсы, DTO, события).
  3. Инверсия зависимостей:
    • Внедряем абстракции (интерфейсы) между модулями.
    • Зависимости направляем от конкретной реализации к абстракции.
  4. Постепенное выделение:
    • Начинаем с наиболее автономного поддомена (например, NotificationService или FileStorageService).
    • Создаем фасад в монолите для обратной совместимости.

Пример рефакторинга:

// Монолит (ДО)
class OrderService {
    private PaymentProcessor processor = new PaymentProcessor();
    private InventoryManager inventory = new InventoryManager();
    // Прямые вызовы и общая база данных
}

// После выделения модуля оплаты (ПОСЛЕ)
class OrderService {
    private final PaymentServiceClient paymentClient; // Внедренный клиент
    // ...
    void processOrder(Order order) {
        paymentClient.charge(order.getId(), order.getAmount()); // Вызов по HTTP/gRPC
    }
}

// Выделенный микросервис/модуль
@Service
public class PaymentServiceClientImpl implements PaymentServiceClient {
    // Вызывает отдельный сервис оплаты
}

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