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

На что обращаешь внимание при разделении монолита на микросервисы?

2.7 Senior🔥 141 комментариев
#REST API и микросервисы#SOLID и паттерны проектирования

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

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

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

На что обращаешь внимание при разделении монолита на микросервисы?

Краткий ответ

Разделение монолита на микросервисы требует системного анализа. Главное — это правильно определить boundary каждого сервиса, обеспечить слабую связность, управлять распределённым состоянием и спланировать развёртывание и мониторинг.

1. Анализ границ и контекстов (Domain-Driven Design)

Domain-Driven Design (DDD)

Примени концепцию Bounded Context из DDD:

Пример: E-commerce система

┌────────────────────────────────────────────────┐
│         Monolith (текущее состояние)          │
├────────────────────────────────────────────────┤
│                                                │
│  ┌─────────────────────────────────────────┐ │
│  │ Users & Authentication                  │ │
│  │ - createUser()                          │ │
│  │ - login()                               │ │
│  │ - updateProfile()                       │ │
│  └─────────────────────────────────────────┘ │
│                                                │
│  ┌─────────────────────────────────────────┐ │
│  │ Products & Catalog                      │ │
│  │ - getProducts()                         │ │
│  │ - updateInventory()                     │ │
│  │ - searchProducts()                      │ │
│  └─────────────────────────────────────────┘ │
│                                                │
│  ┌─────────────────────────────────────────┐ │
│  │ Orders & Payments                       │ │
│  │ - createOrder()                         │ │
│  │ - processPayment()                      │ │
│  │ - trackOrder()                          │ │
│  └─────────────────────────────────────────┘ │
│                                                │
│  ┌─────────────────────────────────────────┐ │
│  │ Notifications                           │ │
│  │ - sendEmail()                           │ │
│  │ - sendSMS()                             │ │
│  └─────────────────────────────────────────┘ │
│                                                │
└────────────────────────────────────────────────┘

После разделения на микросервисы:

┌─────────────────┬────────────────┬──────────────┬─────────────────┐
│                 │                │              │                 │
│  User Service   │ Product Service│ Order Service│Notification Srv │
│  :8001          │  :8002         │  :8003       │  :8004          │
│                 │                │              │                 │
│ ┌─────────────┐ │ ┌────────────┐ │ ┌──────────┐ │ ┌─────────────┐ │
│ │ Users DB    │ │ │ Products DB│ │ │ Orders DB│ │ │ Events Queue│ │
│ └─────────────┘ │ └────────────┘ │ └──────────┘ │ └─────────────┘ │
│                 │                │              │                 │
└─────────────────┴────────────────┴──────────────┴─────────────────┘

Принципы определения границ:

// ПРАВИЛЬНО: отдельный сервис для каждого Bounded Context

// 1. User Service - управляет пользователями
@RestController
@RequestMapping("/api/users")
public class UserController {
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody CreateUserRequest req) {
        // Вся логика управления пользователями тут
        return ResponseEntity.ok(userService.create(req));
    }
}

// 2. Product Service - управляет каталогом
@RestController
@RequestMapping("/api/products")
public class ProductController {
    @GetMapping
    public List<Product> listProducts() {
        // Вся логика каталога тут
        return productService.list();
    }
}

// 3. Order Service - управляет заказами
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    @PostMapping
    public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest req) {
        // Вся логика заказов тут
        return ResponseEntity.ok(orderService.create(req));
    }
}

2. Анализ зависимостей между модулями

Карта зависимостей

// ❌ ПЛОХО: циклические зависимости

// User Service зависит от Order Service
@Service
public class UserService {
    @Autowired
    private OrderService orderService;  // зависимость
    
    public List<Order> getUserOrders(String userId) {
        return orderService.getOrdersByUserId(userId);  // вызов
    }
}

// Order Service зависит от User Service
@Service
public class OrderService {
    @Autowired
    private UserService userService;  // обратная зависимость!
    
    public Order createOrder(CreateOrderRequest req) {
        User user = userService.getUser(req.getUserId());  // вызов
        return orderRepository.save(new Order(user));
    }
}

// Это создаёт проблемы при разделении на сервисы!

Решение: Service-to-Service Communication

// ✓ ПРАВИЛЬНО: асинхронная коммуникация через события

// User Service - не зависит от Order Service
@Service
public class UserService {
    @Autowired
    private EventPublisher eventPublisher;
    
    public User createUser(CreateUserRequest req) {
        User user = userRepository.save(new User(req));
        // Издаём событие для других сервисов
        eventPublisher.publish(new UserCreatedEvent(user.getId()));
        return user;
    }
}

// Order Service - слушает события User Service
@Service
public class OrderService {
    @EventListener
    public void onUserCreated(UserCreatedEvent event) {
        // Инициализируем данные для нового пользователя
        logger.info("User created, preparing order system: {}", event.getUserId());
    }
    
    public Order createOrder(CreateOrderRequest req) {
        // Заказ знает юзера из события, не звоня в User Service
        return orderRepository.save(new Order(req));
    }
}

3. Управление данными и DATABASE PER SERVICE паттерн

❌ ПЛОХО: Одна БД для всех сервисов

services:
  user-service:
    database: shared_database  # одна БД для всех!
  
  product-service:
    database: shared_database
  
  order-service:
    database: shared_database

# Проблемы:
# 1. Сервисы связаны через БД (tight coupling)
# 2. Изменение схемы БД влияет на все сервисы
# 3. Сложное масштабирование
# 4. Нет независимости в выборе БД

✓ ПРАВИЛЬНО: Separate Database Per Service

services:
  user-service:
    database: user_db      # отдельная БД
    db-type: PostgreSQL
  
  product-service:
    database: product_db   # отдельная БД
    db-type: MongoDB       # может быть другой тип!
  
  order-service:
    database: order_db     # отдельная БД
    db-type: PostgreSQL

# Преимущества:
# 1. Слабая связность
# 2. Независимое масштабирование
# 3. Можем менять БД без влияния на другие сервисы
# 4. Оптимизация для каждого сервиса
// Каждый сервис управляет своей БД

// User Service - PostgreSQL
@Configuration
public class UserDataSourceConfig {
    @Bean
    public DataSource userDataSource() {
        return DataSourceBuilder.create()
            .driverClassName("org.postgresql.Driver")
            .url("jdbc:postgresql://user-db:5432/users")
            .username("user")
            .password("password")
            .build();
    }
}

// Product Service - MongoDB
@Configuration
public class ProductDataSourceConfig {
    @Bean
    public MongoClient mongoClient() {
        return MongoClients.create("mongodb://product-db:27017");
    }
}

4. Синхронизация данных между сервисами

❌ Синхронные вызовы

// Order Service вызывает User Service синхронно
@Service
public class OrderService {
    @Autowired
    private RestTemplate restTemplate;
    
    public Order createOrder(CreateOrderRequest req) {
        // Синхронный вызов - блокирует поток
        User user = restTemplate.getForObject(
            "http://user-service/users/" + req.getUserId(),
            User.class
        );
        
        if (user == null) {
            throw new UserNotFoundException();
        }
        
        return orderRepository.save(new Order(user));
    }
}

// Проблемы:
// 1. Если User Service упал - Order Service упадёт
// 2. Медленный отклик (ждём ответа от другого сервиса)
// 3. Сложная обработка ошибок

✓ Асинхронная коммуникация через события (Event-Driven)

// Order Service публикует событие
@Service
public class OrderService {
    @Autowired
    private EventPublisher eventPublisher;
    @Autowired
    private OrderRepository orderRepository;
    
    public void createOrder(CreateOrderRequest req) {
        Order order = new Order();
        order.setUserId(req.getUserId());
        order.setStatus(OrderStatus.PENDING);
        order = orderRepository.save(order);
        
        // Издаём событие - асинхронно
        eventPublisher.publish(new OrderCreatedEvent(
            order.getId(),
            order.getUserId(),
            order.getTotal()
        ));
    }
}

// Notification Service слушает событие
@Service
public class NotificationService {
    @KafkaListener(topics = "order-created")
    public void onOrderCreated(OrderCreatedEvent event) {
        // Отправи уведомление пользователю
        emailService.send(
            "user-" + event.getUserId() + "@example.com",
            "Your order #" + event.getOrderId() + " is confirmed!"
        );
    }
}

// User Service может обновить статистику
@Service
public class UserStatsService {
    @KafkaListener(topics = "order-created")
    public void onOrderCreated(OrderCreatedEvent event) {
        // Обновить счётчик заказов пользователя
        userStatsRepository.incrementOrderCount(event.getUserId());
    }
}

5. Распределённые транзакции (SAGA Pattern)

Проблема: ACID транзакции невозможны

Оригинальная операция в монолите:

BEGIN TRANSACTION
  - Проверить баланс пользователя
  - Зарезервировать товар (уменьшить inventory)
  - Создать заказ
  - Обработать платёж
COMMIT

Если что-то пошло не так - ROLLBACK всё

SAGA Pattern (Orchestration)

// Сагаа оркестрирует процесс
@Service
public class OrderSagaOrchestrator {
    @Autowired
    private UserService userService;
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private OrderRepository orderRepository;
    
    public Order executeOrderSaga(CreateOrderRequest req) throws SagaException {
        Order order = null;
        
        try {
            // Шаг 1: Проверка пользователя
            User user = userService.getUser(req.getUserId());
            if (user == null) {
                throw new SagaException("User not found");
            }
            
            // Шаг 2: Зарезервировать товары
            boolean reserved = inventoryService.reserve(
                req.getProductId(),
                req.getQuantity()
            );
            if (!reserved) {
                throw new SagaException("Product not available");
            }
            
            // Шаг 3: Обработать платёж
            boolean paid = paymentService.processPayment(
                user.getId(),
                req.getTotal()
            );
            if (!paid) {
                // Компенсирующая транзакция: отмени резервирование
                inventoryService.release(req.getProductId(), req.getQuantity());
                throw new SagaException("Payment failed");
            }
            
            // Шаг 4: Создать заказ
            order = orderRepository.save(new Order(user, req));
            
        } catch (SagaException e) {
            // Откати все успешные шаги
            logger.error("Order saga failed: {}", e.getMessage());
            if (order != null && order.getId() != null) {
                orderRepository.delete(order);
            }
            throw e;
        }
        
        return order;
    }
}

6. API Gateway паттерн

Вместо прямых вызовов сервисов

// ❌ ПЛОХО: Клиент вызывает сервисы напрямую
@RestController
public class ClientController {
    @GetMapping("/profile/{userId}")
    public ProfileData getProfile(@PathVariable String userId) {
        // Клиент знает про существование User Service
        User user = restTemplate.getForObject(
            "http://user-service/users/" + userId,
            User.class
        );
        
        // Клиент знает про существование Order Service
        List<Order> orders = restTemplate.getForObject(
            "http://order-service/users/" + userId + "/orders",
            List.class
        );
        
        return new ProfileData(user, orders);
    }
}

✓ API Gateway

// Клиент обращается только к Gateway
@RestController
public class ApiGatewayController {
    @GetMapping("/api/profile/{userId}")
    public ProfileData getProfile(@PathVariable String userId) {
        // Gateway знает о всех сервисах и маршрутизирует
        return profileService.getProfile(userId);
    }
}

// Через spring-cloud-gateway
@SpringBootApplication
public class ApiGatewayApp {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("user-service", r -> r
                .path("/users/**")
                .uri("http://user-service:8001"))
            .route("product-service", r -> r
                .path("/products/**")
                .uri("http://product-service:8002"))
            .route("order-service", r -> r
                .path("/orders/**")
                .uri("http://order-service:8003"))
            .build();
    }
}

7. Сервис дискавери (Service Discovery)

# docker-compose с Consul
version: '3.8'
services:
  consul:
    image: consul:latest
    ports:
      - "8500:8500"
    command: agent -server -bootstrap-expect=1 -ui -client=0.0.0.0
  
  user-service:
    image: user-service:latest
    environment:
      CONSUL_HOST: consul
      SERVICE_NAME: user-service
  
  order-service:
    image: order-service:latest
    environment:
      CONSUL_HOST: consul
      SERVICE_NAME: order-service
      USER_SERVICE_URL: http://user-service:8001  # discovery URL

8. Логирование и трассировка (Distributed Tracing)

// Spring Cloud Sleuth + Zipkin
@Service
public class OrderService {
    @Autowired
    private RestTemplate restTemplate;
    
    public Order createOrder(CreateOrderRequest req) {
        // Sleuth автоматически добавляет trace ID
        logger.info("Creating order for user: {}", req.getUserId());
        
        User user = restTemplate.getForObject(
            "http://user-service/users/" + req.getUserId(),
            User.class
        );
        // Trace ID будет одинаковым в логах всех сервисов!
    }
}

// В логах увидишь:
// user-service: [2024-03-22 10:15:30] [trace-id: abc123] User retrieved
// order-service: [2024-03-22 10:15:31] [trace-id: abc123] Creating order
// notification-service: [2024-03-22 10:15:32] [trace-id: abc123] Sending email

9. Мониторинг и Alerting

# Prometheus + Grafana
version: '3.8'
services:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
  
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
  
  user-service:
    image: user-service:latest
    environment:
      MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE: prometheus

10. Чеклист при разделении монолита

Анализ и планирование:

  • Определены Bounded Context с DDD
  • Идентифицированы зависимости между модулями
  • Нет циклических зависимостей
  • Каждый сервис имеет одну ответственность (SRP)

Архитектура данных:

  • Database per service паттерн
  • Данные не репликируются (Single source of truth)
  • Решена проблема распределённых транзакций (SAGA)

Коммуникация:

  • Определены синхронные и асинхронные операции
  • Есть API Gateway
  • Есть Service Discovery
  • Есть Circuit Breaker для отказоустойчивости

Операции:

  • Настроено логирование и трассировка
  • Есть мониторинг и alerting
  • Есть процесс развёртывания (CI/CD)
  • Есть health checks для каждого сервиса

Итог

При разделении монолита обращай внимание на:

  1. DDD Bounded Contexts — правильные границы сервисов
  2. Слабая связность — минимальные зависимости
  3. Database per Service — независимые хранилища
  4. Event-Driven архитектура — асинхронная коммуникация
  5. API Gateway — единая точка входа
  6. Распределённые транзакции — SAGA паттерн
  7. Логирование и трассировка — видимость во всех сервисах
  8. Мониторинг — алерты и метрики
  9. Отказоустойчивость — Circuit Breaker, retries
  10. Развёртывание — контейнеризация и оркестрация (K8s)