← Назад к вопросам

Почему переходишь с микросервисов на монолит?

3.0 Senior🔥 141 комментариев
#REST API и микросервисы

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

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Переход с микросервисов на монолит: когда и почему

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

Когда микросервисы становятся проблемой

Проблема 1: Усложнение операционной сложности

Микросервисы требуют мощной инфраструктуры и DevOps:

Количество компонентов:
- Монолит (1 приложение): 1 deployment, 1 логирование, 1 мониторинг
- 10 микросервисов: 10 deployments, 10 логирований, 10 мониторингов
- 50 микросервисов: 50 deployments, комплексная трассировка, сложная отладка

Это требует:

  • Kubernetes/Docker expertise
  • Сложные pipeline CI/CD
  • Distributed tracing (Jaeger, Zipkin)
  • Service mesh (Istio, Linkerd)
  • Отдельная DevOps команда

Реальный пример:

Жизненный цикл простого изменения:

Монолит:
1. Разработка → 2. Тестирование → 3. Deploy → Готово (15 минут)

Микросервисы (10 сервисов):
1. Разработка → 2. Unit tests → 3. Integration tests → 4. Build Docker image
5. Push в registry → 6. Deploy в staging → 7. E2E tests → 8. Deploy в prod
9. Health checks → 10. Мониторинг → Готово (2-3 часа)

Проблема 2: Сетевые задержки и отказы

// Монолит — синхронный вызов, быстро и безопасно
User user = userService.getUser(id);
Order order = orderService.getOrder(userId);
Payment payment = paymentService.getPayment(orderId);
// ~5-10 мс

// Микросервисы — HTTP/RPC вызовы
User user = restTemplate.getForObject("http://user-service:8081/users/" + id, User.class);
Order order = restTemplate.getForObject("http://order-service:8082/orders/" + userId, Order.class);
Payment payment = restTemplate.getForObject("http://payment-service:8083/payments/" + orderId, Payment.class);
// ~50-200 мс (сетевые задержки)

// Если один сервис упал — цепочка падает
if (user == null || order == null || payment == null) {
    // Частая ошибка при микросервисах
}

Проблема 3: Distributed transactions nightmare

С монолитом просто:

@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
    // ACID гарантирует консистентность
    account1.withdraw(amount);
    account2.deposit(amount);
    // Все или ничего
}

С микросервисами нужна Saga pattern:

// Сложный Saga orchestration
@Service
public class TransferSaga {
    @Transactional
    public void transferMoney(TransferCommand cmd) {
        // Шаг 1
        WithdrawEvent withdrawEvent = accountService.withdraw(cmd.getFromId(), cmd.getAmount());
        if (!withdrawEvent.isSuccess()) {
            // Компенсация
            return;
        }
        
        // Шаг 2 (может упасть)
        DepositEvent depositEvent = accountService.deposit(cmd.getToId(), cmd.getAmount());
        if (!depositEvent.isSuccess()) {
            // Компенсирующая транзакция
            accountService.deposit(cmd.getFromId(), cmd.getAmount()); // Возврат
            publishEvent(new TransactionFailedEvent());
            return;
        }
        
        publishEvent(new TransactionCompletedEvent());
    }
}

// Потенциальные race conditions и deadlocks!

Проблема 4: Data consistency nightmare

Без единой БД возникают проблемы:

Монолит: единая БД, транзакции ACID
user.balance = 100
order.status = "created"

При сбое всё откатывается вместе.

Микросервисы: несколько БД
user-service BD: balance = 50 (withdrawn)
order-service BD: status = "created" (но deposit не произошёл!)
order-service BD: status = "pending_payment" (но payment упал)

Итого: Данные в несогласованном состоянии, нужна сложная reconciliation.

Когда монолит имеет смысл

1. Маленькая команда (до 10 разработчиков)

Монолит:
- 1 deployment
- Все могут понять весь проект
- Легко координировать
- Нет микро-управления сервисами

Микросервисы:
- Каждому нужен свой сервис
- Конфликты между командами
- Сложная координация
- Dead code в разных сервисах

2. Высокая когезия, низкая связанность между компонентами

// Хороший монолит: модули с четкими границами
package com.example.ecommerce;

├── users/
│   ├── UserService
│   ├── UserRepository
│   └── User entity
├── orders/
│   ├── OrderService
│   ├── OrderRepository
│   └── Order entity
└── payments/
    ├── PaymentService
    ├── PaymentRepository
    └── Payment entity

// Внутренние вызовы, а не RPC!

3. Performance is critical

Монолит:
- Локальные вызовы: ~1 мс
- Транзакции ACID
- Кэширование просто

Микросервисы:
- Сетевые вызовы: ~50-200 мс
- Eventual consistency
- Сложное кэширование (cache invalidation nightmare)

4. Domain events нечасто пересекаются

Если большинство операций требует синхронных вызовов между сервисами — лучше монолит.

Real-world примеры компаний, вернувшихся к монолиту

Amazon Prime Video (2023):

Проблема: микросервисная архитектура работала медленнее
- Стоимость Lambda invocations: $2 млн/год
- Сетевые задержки между сервисами
- Сложность отладки распределённых систем

Решение: переход на монолит
- Производительность ↑ 3x
- Стоимость ↓ 90%
- Стабильность ↑

Basecamp (2020):

Вернулись с микросервисов на монолит (Ruby on Rails)
Причины:
- Сложность DevOps
- Высокие задержки
- Трудность отладки
- Маленькая команда (не нужна масштабируемость)

Результат: меньше ошибок, быстрее разработка

Сигналы для миграции на монолит

Наблюдай эти метрики:

// 1. Latency увеличился
// Раньше: 100 мс
// Теперь: 500+ мс (из-за сетей)

// 2. DevOps overhead растёт
// Раньше: 1 DevOps на 10 разработчиков
// Теперь: 1 DevOps на 3 разработчика

// 3. Consistency проблемы
// Больше reconciliation jobs
// Больше data corruption issues

// 4. Deployment frequency упала
// Раньше: 10 deployments в день
// Теперь: 2-3 deployment в неделю (из-за координации)

// 5. Team productivity упала
// Разработчик тратит время на DevOps, а не на features

Как правильно организовать монолит

Модульная архитектура внутри монолита:

// Четкие boundaries между модулями
@Configuration
@ComponentScan("com.example.users")
public class UserModule { }

@Configuration
@ComponentScan("com.example.orders")
public class OrderModule { }

// Event-driven коммуникация внутри монолита
@Component
public class OrderService {
    @Transactional
    public void createOrder(CreateOrderCommand cmd) {
        Order order = new Order(cmd);
        orderRepository.save(order);
        
        // Событие публикуется внутри monolith, не RPC
        applicationEventPublisher.publishEvent(
            new OrderCreatedEvent(order.getId())
        );
    }
}

@Component
public class PaymentListener {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        // Синхронно, но в отдельной транзакции (если нужно)
        paymentService.initializePayment(event.getOrderId());
    }
}

Заключение

Переход на монолит имеет смысл, когда:

  • Команда маленькая (операционная сложность микросервисов не оправдана)
  • Перформанс критичен (сетевые задержки убивают UX)
  • Consistency важна (Saga pattern усложнил систему)
  • Связанность высокая (сервисы постоянно разговаривают друг с другом)
  • Стоимость DevOps высока (infrastructure overhead)

Монолит + правильная модульная архитектура часто лучше, чем неправильные микросервисы. Выбор архитектуры должен соответствовать размеру команды и требованиям проекта.

Почему переходишь с микросервисов на монолит? | PrepBro