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

Какие проблемы могут возникнуть при разделении монолита на микросервисы

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

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

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

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

Проблемы при разделении монолита на микросервисы

Миграция с монолитной архитектуры на микросервисы — это одна из самых сложных трансформаций в разработке. Она обещает масштабируемость и гибкость, но несёт с собой множество технических и организационных проблем.

1. Сложность разделения данных (Data Partitioning)

Проблема: Расщепление базы данных

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

-- Монолит: единая база данных
SELECT 
    u.id, u.name,
    o.order_id, o.total,
    p.product_name
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN products p ON o.product_id = p.id
WHERE u.id = 123;
// Микросервисы: данные в разных БД
@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private UserServiceClient userServiceClient; // HTTP запрос!
    @Autowired
    private ProductServiceClient productServiceClient; // HTTP запрос!
    
    public OrderDetails getOrderDetails(Long orderId) {
        Order order = orderRepository.findById(orderId);
        UserDTO user = userServiceClient.getUser(order.getUserId()); // Сетевой запрос
        ProductDTO product = productServiceClient.getProduct(order.getProductId()); // Сетевой запрос
        return combine(order, user, product);
    }
}

Проблемы:

  • Потеря ACID гарантий: транзакции через HTTP не существуют
  • N+1 запросы: один запрос превращается в несколько
  • Задержки сети: HTTP медленнее, чем SQL JOIN

Решение: Eventual Consistency

// Event-driven подход
@Service
public class OrderService {
    @Autowired
    private EventPublisher eventPublisher;
    
    public void createOrder(Order order) {
        orderRepository.save(order);
        // Вместо синхронного запроса — асинхронное событие
        eventPublisher.publishOrderCreated(order.getId());
    }
}

@Service
public class NotificationService {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        // Получит информацию асинхронно
        sendOrderConfirmationEmail(event.getOrderId());
    }
}

2. Распределённые транзакции (Distributed Transactions)

Проблема: Два фазовый коммит не работает

Микросервисы не могут работать с ACID гарантиями:

// Монолит: ACID гарантирована
@Transactional
public void transferMoney(Long fromUserId, Long toUserId, BigDecimal amount) {
    User from = userRepository.findById(fromUserId);
    User to = userRepository.findById(toUserId);
    
    from.debit(amount);
    to.credit(amount);
    
    userRepository.save(from);
    userRepository.save(to);
    // Либо оба успешны, либо оба откатываются
}
// Микросервисы: нужен Saga Pattern
@Service
public class MoneyTransferSaga {
    @Autowired
    private AccountServiceClient accountService;
    @Autowired
    private NotificationServiceClient notificationService;
    
    @Transactional
    public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
        try {
            // Шаг 1: Дебет первого счёта
            accountService.debit(fromId, amount);
            
            try {
                // Шаг 2: Кредит второго счёта
                accountService.credit(toId, amount);
                
                // Уведомление
                notificationService.sendTransferSuccess(fromId, toId, amount);
            } catch (Exception e) {
                // Откатываем дебет
                accountService.credit(fromId, amount);
                notificationService.sendTransferFailed(fromId, toId, amount);
                throw e;
            }
        } catch (Exception e) {
            // Обработка ошибки
            handleTransactionFailure(fromId, toId, amount);
        }
    }
}

3. Консистентность данных (Data Consistency)

Проблема: Вы не можете гарантировать consistency

Данные в разных сервисах могут рассинхронизироваться:

// Последовательность событий
1. OrderService создаёт заказ
   Order создан в БД OrderService ✓
   
2. Отправляет событие OrderCreated
   
3. NotificationService получает событие
   Отправляет email ✓
   
4. InventoryService получает событие
   Уменьшает количество товаров ✓
   
// Но что если NotificationService упал и потеряет сообщение?
// Что если InventoryService переобработает событие дважды?

Решение: Idempotency и retries

@Service
public class InventoryService {
    @Autowired
    private InventoryRepository inventoryRepository;
    
    // Идемпотентная операция
    @Idempotent(idempotencyKey = "event.orderId")
    public void onOrderCreated(OrderCreatedEvent event) {
        Inventory inventory = inventoryRepository.findById(event.getProductId());
        
        // Проверяем, не обработали ли мы это событие раньше
        if (inventory.hasProcessedEvent(event.getId())) {
            return; // Уже обработали
        }
        
        inventory.decreaseQuantity(event.getQuantity());
        inventory.markEventProcessed(event.getId());
        inventoryRepository.save(inventory);
    }
}

4. Коммуникация между сервисами (Inter-Service Communication)

Проблема 1: Синхронная коммуникация неэффективна

// Синхронный REST вызов
@Service
public class OrderService {
    @Autowired
    private RestTemplate restTemplate;
    
    public void processOrder(Order order) {
        // Блокирует текущий поток
        UserDTO user = restTemplate.getForObject(
            "http://user-service/api/users/" + order.getUserId(),
            UserDTO.class
        ); // Ждём ответа
        
        // Если user-service медленный — это сказывается на всём сервисе
    }
}

Проблемы синхронной коммуникации:

  • Cascading failures (цепные отказы)
  • Медленные сервисы замораживают быстрые
  • Высокая связанность

Проблема 2: Асинхронная коммуникация сложнее

// Асинхронный подход через message broker
@Service
public class OrderService {
    @Autowired
    private KafkaTemplate kafkaTemplate;
    
    public void createOrder(Order order) {
        order.setStatus("PENDING");
        orderRepository.save(order);
        
        // Отправляем событие в Kafka
        kafkaTemplate.send("order-events", 
            new OrderCreatedEvent(order.getId(), order.getUserId()));
    }
}

@Service
public class PaymentService {
    @KafkaListener(topics = "order-events")
    public void handleOrderCreated(OrderCreatedEvent event) {
        // Обрабатываем асинхронно
        // Что если это сообщение обработается дважды?
        // Что если сервис упадёт до обработки?
    }
}

5. Сетевые задержки и отказы (Network Latency & Failures)

Проблема: Сеть ненадежна

// Без обработки отказов
@Service
public class OrderService {
    @Autowired
    private UserServiceClient userService;
    
    public UserDTO getUser(Long userId) {
        return userService.getUser(userId); // Может упасть!
    }
}

// С обработкой отказов
@Service
public class OrderServiceResilient {
    @Autowired
    private UserServiceClient userService;
    
    @CircuitBreaker(name = "userService", fallbackMethod = "fallbackGetUser")
    @Retry(name = "userService", fallbackMethod = "fallbackGetUser")
    @Timeout(name = "userService")
    public UserDTO getUser(Long userId) {
        return userService.getUser(userId);
    }
    
    public UserDTO fallbackGetUser(Long userId, Exception ex) {
        // Возвращаем кешированные данные или значение по умолчанию
        return userCache.getOrDefault(userId, new UserDTO());
    }
}

Нужно реализовать:

  • Circuit breaker (разрывной переключатель)
  • Retry с exponential backoff
  • Timeout'ы
  • Bulkhead pattern (изоляция ресурсов)

6. Тестирование становится сложнее

Проблема: E2E тестирование микросервисов сложнее

// Монолит: просто запустить приложение
@SpringBootTest
public class OrderServiceTest {
    @Autowired
    private OrderService orderService;
    
    @Test
    public void testCreateOrder() {
        Order order = orderService.createOrder(new CreateOrderRequest());
        assertThat(order.getId()).isNotNull();
    }
}
// Микросервисы: нужно запустить несколько сервисов
@SpringBootTest
public class OrderServiceIntegrationTest {
    // Запускаем зависимые сервисы
    @Container
    static UserServiceContainer userService = new UserServiceContainer();
    
    @Container
    static PaymentServiceContainer paymentService = new PaymentServiceContainer();
    
    @Container
    static NotificationServiceContainer notificationService = new NotificationServiceContainer();
    
    @Test
    public void testCreateOrder() {
        // Нужно мокировать все зависимые сервисы
        // Или запускать их в Docker контейнерах
        // Тест становится медленным и хрупким
    }
}

7. Развёртывание и мониторинг (Deployment & Monitoring)

Проблема: Операционная сложность

# Монолит: развёртываем один JAR
$ docker run -e SPRING_PROFILES_ACTIVE=prod myapp:1.0

# Микросервисы: развёртываем 10+ сервисов
$ docker-compose up
# Или в Kubernetes:
kubectl apply -f user-service.yaml
kubectl apply -f order-service.yaml
kubectl apply -f payment-service.yaml
kubectl apply -f notification-service.yaml
# ... и т.д

Проблемы:

  • Нужна инфраструктура (Docker, Kubernetes)
  • Более сложный мониторинг
  • Логирование распределённое
  • Трассировка запросов сложнее

Решение: Distributed Tracing

// Используем Sleuth и Zipkin для отслеживания запросов
@Service
public class OrderService {
    @Autowired
    private Tracer tracer; // Spring Cloud Sleuth
    
    public void processOrder(Order order) {
        // Создаём span для отслеживания
        Span span = tracer.nextSpan().name("processOrder")
            .tag("orderId", order.getId());
        
        try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
            // Вызов переходит к следующему сервису с trace context
            paymentService.processPayment(order);
        }
    }
}

8. Управление конфигурацией (Configuration Management)

Проблема: 10 сервисов = 10 конфиг-файлов

# user-service/application.yml
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/users
  kafka:
    bootstrap-servers: localhost:9092

# order-service/application.yml
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/orders
  kafka:
    bootstrap-servers: localhost:9092

# payment-service/application.yml
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/payments
  kafka:
    bootstrap-servers: localhost:9092

# Как синхронизировать конфигурацию?
# Как выкатить изменение в production?

Решение: Config Server

# Config Server (центральная конфигурация)
spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/mycompany/configs
// Микросервисы подтягивают конфигурацию
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

9. Версионирование API (API Versioning)

Проблема: Как обновить контракт между сервисами?

// UserService v1
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    @GetMapping("/{id}")
    public UserDTO getUser(@PathVariable Long id) {
        return new UserDTO(id, "John", "john@example.com");
    }
}

// OrderService зависит от этого контракта
@Service
public class OrderService {
    @Autowired
    private RestTemplate restTemplate;
    
    public void createOrder(Long userId) {
        UserDTO user = restTemplate.getForObject(
            "http://user-service/api/v1/users/" + userId,
            UserDTO.class
        );
    }
}

// Нужно добавить новое поле в User
// Обновляем UserService на v2
@GetMapping("/{id}")
public UserDTOv2 getUser(@PathVariable Long id) {
    return new UserDTOv2(id, "John", "john@example.com", "PREMIUM");
}

// Что если OrderService ещё на v1?
// Нужна сложная стратегия миграции

10. Организационные вызовы

Проблема: Team Structure

Монолит:
  Backend Team
    ├─ User Module
    ├─ Order Module
    ├─ Payment Module
    └─ Notification Module

Микросервисы:
  User Service Team
  Order Service Team
  Payment Service Team
  Notification Service Team
  DevOps Team
  Platform Team
  ...

Проблемы:

  • Нужно больше людей
  • Сложнее синхронизировать релизы
  • Командное взаимодействие важнее
  • Нужны чётко определённые контракты

Когда НЕ переходить на микросервисы

✗ Маленькая команда (< 5 человек)
✗ Монолит работает хорошо и неспешно растёт
✗ Нет опыта с распределёнными системами
✗ Требуется strong consistency
✗ Сложные синхронные операции между модулями

Выводы

Миграция на микросервисы — это не просто архитектурное изменение, это организационное. Основные проблемы:

  1. Данные: Как разделить базу без потери гарантий?
  2. Консистентность: Eventual consistency требует иного мышления
  3. Сеть: Сетевые вызовы медленнее и ненадежнее БД
  4. Тестирование: Сложнее тестировать систему в целом
  5. Операции: Нужна инфраструктура (Docker, K8s, monitoring)
  6. Люди: Нужны люди, которые понимают распределённые системы

Правило 80/20: Переходи на микросервисы, только если

  • Монолит стал бутылочным горлышком
  • Есть опыт в команде
  • Есть ресурсы на инфраструктуру
  • Команда готова к этому сложному переходу