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

Какие знаешь принципы декомпозиции монолита?

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

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

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

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

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

Переход от монолитного приложения к архитектуре микросервисов - это сложный процесс, требующий правильного подхода. Существуют проверенные принципы, которые помогают минимизировать риски и повысить успешность миграции.

1. Domain-Driven Design (DDD)

DDD - фундаментальный принцип для правильной декомпозиции. Нужно разделить монолит по бизнес-доменам, а не по техническим слоям.

// ❌ Неправильно - разделение по слоям
Microservice 1: UserController, UserService, UserRepository
Microservice 2: OrderController, OrderService, OrderRepository

// ✅ Правильно - разделение по доменам
Microservice: UserManagement (User bounded context)
Microservice: OrderManagement (Order bounded context)
Microservice: PaymentProcessing (Payment bounded context)

Каждый bounded context (ограниченный контекст) должен иметь:

  • Свою бизнес-логику
  • Свою базу данных
  • Четко определенные границы

2. Database per Service

Критический принцип: каждый микросервис имеет свою БД. Это обеспечивает:

  • Независимость развертывания
  • Возможность выбирать оптимальную БД (PostgreSQL, MongoDB, Redis)
  • Избежание связности через общую БД
Monolith:          Microservices:
┌─────────────┐   ┌──────────────┐   ┌──────────────┐
│  All Logic  │   │  User Service│   │Order Service │
└─────────────┘   │  PostgreSQL  │   │  PostgreSQL  │
      │            └──────────────┘   └──────────────┘
      ▼
┌─────────────────┐
│   Single DB     │
└─────────────────┘

3. Strangler Fig Pattern

Это самый безопасный подход к миграции. Новые функции реализуются как микросервисы, старые постепенно вытесняются.

// Proxy/API Gateway перенаправляет запросы
Gateway logic:
if (request.getPath().startsWith("/api/users")) {
  return routeToMicroservice("user-service");
} else {
  return routeToMonolith();
}

Преимущества:

  • Ноль простоя
  • Возможность откатиться
  • Пошаговое внедрение
  • Возможность параллельного развития

4. Event-Driven Architecture

Вместо синхронных вызовов (RPC), используем асинхронную коммуникацию через события.

// ❌ Синхронный подход (тесная связь)
userService.createUser(user);
orderService.notifyUserCreated(user);
notificationService.sendWelcomeEmail(user);

// ✅ Event-driven подход (слабая связь)
@Service
public class UserService {
  @Autowired
  private ApplicationEventPublisher publisher;
  
  public void createUser(User user) {
    // Сохраняем пользователя
    userRepository.save(user);
    // Публикуем событие
    publisher.publishEvent(new UserCreatedEvent(user));
  }
}

// Other services подписаны на событие
@Component
public class UserCreatedEventListener {
  @EventListener
  public void onUserCreated(UserCreatedEvent event) {
    // Отправляем email, создаем профиль и т.д.
    notificationService.sendWelcomeEmail(event.getUser());
  }
}

Обычно используются message brokers (RabbitMQ, Apache Kafka, AWS SNS/SQS).

5. API Gateway Pattern

API Gateway - единая точка входа для всех клиентов. Он маршрутизирует запросы к нужным сервисам.

// Пример с Spring Cloud Gateway
@SpringBootApplication
public class GatewayApplication {
  @Bean
  public RouteLocator customRoutes(RouteLocatorBuilder builder) {
    return builder.routes()
      .route("user_service", r -> r
        .path("/api/users/**")
        .uri("http://user-service:8001"))
      .route("order_service", r -> r
        .path("/api/orders/**")
        .uri("http://order-service:8002"))
      .build();
  }
}

6. Saga Pattern для распределенных транзакций

Поскольку микросервисы имеют разные БД, обычные транзакции невозможны. Saga pattern решает эту проблему:

// Choreography-based Saga
// Order Service создает заказ и публикует событие
publisher.publishEvent(new OrderCreatedEvent(order));

// Payment Service слушает и обрабатывает платеж
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
  Payment payment = processPayment(event.getOrder());
  if (payment.isSuccessful()) {
    publisher.publishEvent(new PaymentCompletedEvent(payment));
  } else {
    publisher.publishEvent(new PaymentFailedEvent(payment));
  }
}

// Order Service слушает результат и обновляет статус
@EventListener
public void onPaymentCompleted(PaymentCompletedEvent event) {
  orderRepository.updateStatus(event.getPayment().getOrderId(), "CONFIRMED");
}

7. Service Discovery

В микросервисной архитектуре нужен механизм обнаружения сервисов. Обычно используются:

  • Eureka (Netflix, встроена в Spring Cloud)
  • Consul (HashiCorp)
  • Kubernetes Service Discovery
@SpringBootApplication
@EnableEurekaClient
public class UserServiceApplication {
  public static void main(String[] args) {
    SpringApplication.run(UserServiceApplication.class, args);
  }
}

// Другой сервис может найти UserService по имени
@FeignClient(name = "user-service")
public interface UserServiceClient {
  @GetMapping("/api/users/{id}")
  User getUserById(@PathVariable Long id);
}

8. Monitoring и Logging

В распределенной системе критично иметь:

  • Centralized logging (ELK, Splunk) для агрегации логов
  • Distributed tracing (Jaeger, Zipkin) для отслеживания запросов
  • Metrics (Prometheus, Micrometer) для мониторинга производительности
@RestController
public class UserController {
  @GetMapping("/users/{id}")
  @Timed(value = "user.get.time", description = "Time taken to get user")
  public User getUser(@PathVariable Long id) {
    // Логирование и трейсинг происходит автоматически
    return userService.getUser(id);
  }
}

9. Contract Testing

Когда сервисы взаимодействуют, критично проверять контракты между ними:

// Spring Cloud Contract для тестирования API контрактов
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceContractTest {
  @Test
  public void shouldReturnUserWithId() {
    // Consumer side: проверяем, что получим ожидаемый формат
    given().pathParam("id", 1)
      .when().get("/api/users/{id}")
      .then()
      .statusCode(200)
      .body("id", equalTo(1))
      .body("name", notNullValue());
  }
}

10. Постепенное разделение

Критический момент: не разделяйте всё сразу. Начните с:

  1. Самых независимых модулей (те, которые имеют мало зависимостей)
  2. Часто меняющихся модулей
  3. Высоконагруженных модулей (масштабирование)

Типичный порядок миграции

1. Выделить User Service (базовая функциональность)
2. Выделить Payment Service (независимый домен)
3. Выделить Notification Service (асинхронный)
4. Выделить Order Service (зависит от User и Payment)
5. Рефакторить оставшийся монолит

Практическая мудрость

Декомпозиция монолита - это марафон, не спринт. Основные ошибки:

  • Попытка разделить всё сразу
  • Создание микросервисов на основе технического слоя, а не домена
  • Пренебрежение синхронизацией данных между сервисами
  • Недостаточное внимание к monitoring и logging

Правильный подход: DDD + Strangler Fig + Event-driven = успешная миграция.