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

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

2.0 Middle🔥 191 комментариев
#REST API и микросервисы#Брокеры сообщений

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

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

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

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

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

1. Синхронное взаимодействие (Request-Reply)

Когда использовать:

✓ Требуется немедленный ответ
✓ Данные нужны для продолжения операции
✓ Невысокая нагрузка (< 100 req/sec на endpoint)
✓ Операция должна завершиться успешно или откатиться
✓ Простая связь между компонентами

Примеры:

// REST API для получения пользователя
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userService.findById(id); // Синхронный вызов
    if (user == null) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(user);
}

// Оплата заказа (нужен немедленный результат)
@PostMapping("/orders/{id}/pay")
public ResponseEntity<PaymentResult> payOrder(@PathVariable Long id) {
    PaymentResult result = paymentService.processPayment(id); // Синхронно
    if (result.isSuccess()) {
        return ResponseEntity.ok(result);
    } else {
        return ResponseEntity.status(400).body(result);
    }
}

// Проверка доступности товара
@GetMapping("/products/{id}/available")
public ResponseEntity<Boolean> isAvailable(@PathVariable Long id) {
    boolean available = inventoryService.checkStock(id); // Синхронная проверка
    return ResponseEntity.ok(available);
}

Недостатки:

  • Блокирует caller до получения ответа
  • Медленнее асинхронного подхода
  • Зависимость между системами (если сервер упал — caller ждёт timeout)

2. Асинхронное взаимодействие (Event-driven)

Когда использовать:

✓ Операция может выполниться позже
✓ Высокая нагрузка (> 100 req/sec)
✓ Не нужен немедленный ответ на действие
✓ Развязывание между системами
✓ Потенциальные сбои в целевой системе

Примеры:

// Создание заказа — отправляем событие асинхронно
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
    Order order = orderService.create(request);
    
    // Асинхронное событие
    eventPublisher.publishEvent(new OrderCreatedEvent(
        order.getId(),
        order.getUserId(),
        order.getTotal()
    ));
    
    // Немедленно возвращаем ответ
    return ResponseEntity.status(201).body(order);
    // Уведомление пользователю, обновление inventory и т.д. произойдут позже
}

// Отправка email (не критична для бизнеса)
@Component
public class EmailNotificationListener {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        // Может выполниться с задержкой
        emailService.sendOrderConfirmation(event.getOrderId());
    }
}

// Обновление кэша поисковых индексов
@Component
public class SearchIndexListener {
    @KafkaListener(topics = "orders-created")
    public void indexOrder(Order order) {
        // Асинхронное обновление индексов
        searchService.indexOrder(order);
    }
}

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

  • Высокая масштабируемость
  • Развязанные системы
  • Лучшая производительность
  • Толерантность к сбоям

Недостатки:

  • Сложность отладки
  • Eventual consistency (данные обновляются не моментально)
  • Нужна Message Queue (Kafka, RabbitMQ)

3. Fire-and-Forget (асинхронный без ответа)

Когда использовать:

✓ Не нужен результат операции
✓ Операция некритична
✓ Очень высокая нагрузка
✓ Максимальная производительность

Примеры:

// Логирование активности пользователя
@PostMapping("/products/{id}")
public ResponseEntity<Product> viewProduct(@PathVariable Long id) {
    Product product = productService.getProduct(id);
    
    // Fire-and-Forget: отправляем событие, не ждём результата
    analyticsService.trackView(id, getCurrentUserId());
    // или
    kafkaTemplate.send("analytics-topic", new ViewEvent(id));
    
    return ResponseEntity.ok(product);
}

// Отправка telemetry
@Component
public class TelemetryService {
    @Async
    public void sendMetrics(String metric, double value) {
        // Асинхронно, без ожидания результата
        metricsCollector.record(metric, value);
    }
}

// Логирование
logger.info("User logged in: " + userId); // Fire-and-Forget

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

  • Минимальная латентность
  • Максимальная пропускная способность
  • Идеально для некритичных операций

4. Pub-Sub (множественные подписчики)

Когда использовать:

✓ Один источник события, множество обработчиков
✓ Неизвестное количество обработчиков в будущем
✓ Слабая связанность между компонентами
✓ Масштабируемая система

Примеры:

// Событие: заказ создан
// Подписчики: email, SMS, push, analytics, inventory, recommendation engine

@Component
public class OrderEventBus {
    @Autowired
    private EventPublisher eventPublisher;
    
    public void createOrder(Order order) {
        // Один источник события
        eventPublisher.publish(new OrderCreatedEvent(order));
    }
}

// Подписчик 1: Email уведомление
@Component
public class EmailNotificationService {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        sendConfirmationEmail(event.getOrder());
    }
}

// Подписчик 2: SMS уведомление
@Component
public class SmsNotificationService {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        sendSms(event.getOrder());
    }
}

// Подписчик 3: Обновление inventory
@Component
public class InventoryService {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        reserveStock(event.getOrder());
    }
}

// Подписчик 4: Analytics
@Component
public class AnalyticsService {
    @KafkaListener(topics = "order-created")
    public void onOrderCreated(Order order) {
        trackOrderCreation(order);
    }
}

// В будущем: добавить подписчика на рекомендации
// БЕЗ изменения кода OrderEventBus!

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

  • Максимальная развязанность
  • Легко добавлять новых обработчиков
  • Масштабируемая архитектура

5. Request-Reply с Timeout (синхронно с резервными вариантами)

Когда использовать:

✓ Нужен ответ, но есть риск сбоя
✓ Среднего уровня критичность
✓ Есть дефолтное поведение

Примеры:

// REST с Circuit Breaker и fallback
@Component
public class UserServiceClient {
    @Autowired
    private RestTemplate restTemplate;
    
    @HystrixCommand(
        fallbackMethod = "getUserFallback",
        commandProperties = {
            @HystrixProperty(
                name = "execution.isolation.thread.timeoutInMilliseconds",
                value = "3000"  // 3 второй таймаут
            )
        }
    )
    public User getUser(Long userId) {
        return restTemplate.getForObject(
            "http://user-service/users/" + userId,
            User.class
        );
    }
    
    // Fallback при таймауте или ошибке
    public User getUserFallback(Long userId) {
        // Возвращаем кэшированную версию или пустой объект
        return userCache.getOrDefault(userId, new AnonymousUser());
    }
}

6. Batch processing (пакетная обработка)

Когда использовать:

✓ Не критична скорость
✓ Высокий объём данных
✓ Ресурсоёмкая операция
✓ Можно обработать ночью

Примеры:

// Ночной batch job для обработки отчётов
@Component
public class ReportProcessor {
    @Scheduled(cron = "0 2 * * *") // 2 AM every day
    public void generateDailyReports() {
        List<Report> reports = reportService.getAll();
        
        for (Report report : reports) {
            processReport(report); // Может быть долгой операцией
        }
        
        emailService.sendReports(reports);
    }
}

// Spring Batch для параллельной обработки
@Configuration
public class BatchConfiguration {
    @Bean
    public Job importUserJob() {
        return jobBuilderFactory.get("importUserJob")
            .start(step1())
            .build();
    }
    
    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
            .<User, User>chunk(100)  // Обрабатывать по 100 за раз
            .reader(itemReader())
            .processor(itemProcessor())
            .writer(itemWriter())
            .build();
    }
}

Матрица выбора взаимодействия

ТипЛатентностьScalabilityСложностьСлучаи использования
СинхронныйНизкаяНизкаяНизкаяREST API, Получение данных
Асинхронный (Event)СредняяВысокаяСредняяNotifikации, Analytics
Fire-and-ForgetОчень низкаяОчень высокаяНизкаяЛогирование, Metrics
Pub-SubСредняяВысокаяСредняяМножество подписчиков
BatchВысокаяСредняяВысокаяBulk operations, Ночные jobs
Sync + FallbackНизкаяНизкаяСредняяКритичные с backup

Hybrid подход (рекомендуется)

Лучшая практика — комбинировать:

// REST endpoint — синхронный
@PostMapping("/checkout")
public ResponseEntity<CheckoutResult> checkout(@RequestBody CheckoutRequest req) {
    // Синхронная валидация (критична)
    if (!inventoryService.hasStock(req.getProductId())) {
        return ResponseEntity.badRequest().body(new CheckoutResult("Out of stock"));
    }
    
    // Критичная операция: payment (синхронная с retry)
    PaymentResult payment = paymentService.charge(req.getAmount());
    if (!payment.isSuccess()) {
        return ResponseEntity.badRequest().body(new CheckoutResult("Payment failed"));
    }
    
    // Некритичная операция: отправить уведомления (асинхронная)
    eventPublisher.publishEvent(new OrderCompletedEvent(req));
    
    // Fire-and-forget: логирование и аналитика
    analyticsService.trackCheckout(req);
    
    return ResponseEntity.ok(new CheckoutResult("Success"));
}

Заключение

Выбор типа взаимодействия зависит от:

  1. Критичность — критичные операции синхронные
  2. Масштабируемость — высоконагруженные системы асинхронные
  3. Связанность — слабо связанные системы используют Pub-Sub
  4. Скорость — Fire-and-Forget для максимальной производительности
  5. Объём — Batch для большого объёма данных

Лучшая архитектура использует все типы:

  • Синхронное для критичных операций (payment, validation)
  • Асинхронное для уведомлений и analytics
  • Fire-and-forget для логирования
  • Pub-Sub для развязанных компонентов
  • Batch для массовой обработки

Это обеспечивает надежность, масштабируемость и высокую производительность одновременно.

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