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

Какой подход к коммуникации между микросервисами ты предпочитаешь?

3.0 Senior🔥 121 комментариев
#REST API и HTTP#Архитектура и паттерны#Брокеры сообщений

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

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

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

Коммуникация между микросервисами

Это один из ключевых архитектурных вопросов. Я предпочитаю гибридный подход, выбирая инструмент в зависимости от сценария. Давай разберём все варианты и когда каждый применяется.

1. Синхронная коммуникация: REST/HTTP

Плюсы:

  • Простота и понятность
  • Легко отладить (curl, Postman)
  • Хорошо задокументирована
  • Идеальна для простых GET запросов

Минусы:

  • Сильная связанность сервисов
  • Если сервис недоступен — ошибка сразу
  • Синхронное ожидание блокирует ресурсы

Когда использовать: для надёжных внутренних запросов, где нужна гарантия выполнения (получение профиля пользователя, проверка доступа).

import httpx
from tenacity import retry, stop_after_attempt, wait_exponential

class UserService:
    def __init__(self):
        self.client = httpx.AsyncClient()
    
    @retry(stop=stop_after_attempt(3), wait=wait_exponential())
    async def get_user(self, user_id: int):
        response = await self.client.get(f"http://user-service/users/{user_id}")
        response.raise_for_status()
        return response.json()

2. Асинхронная коммуникация: Message Queue

Я предпочитаю RabbitMQ или Kafka для асинхронной коммуникации. Это решает множество проблем.

RabbitMQ (очереди сообщений):

  • Простая двухточечная коммуникация
  • Гарантия доставки
  • Мертвые письма (Dead Letter Queue) для ошибок
import pika
import json
from typing import Callable

class EventPublisher:
    def __init__(self, rabbitmq_url: str):
        self.connection = pika.BlockingConnection(
            pika.URLParameters(rabbitmq_url)
        )
        self.channel = self.connection.channel()
    
    def publish(self, exchange: str, routing_key: str, message: dict) -> None:
        self.channel.exchange_declare(
            exchange=exchange,
            exchange_type='topic',
            durable=True
        )
        self.channel.basic_publish(
            exchange=exchange,
            routing_key=routing_key,
            body=json.dumps(message),
            properties=pika.BasicProperties(delivery_mode=2)  # Persistent
        )

class EventConsumer:
    def __init__(self, rabbitmq_url: str, queue_name: str):
        self.connection = pika.BlockingConnection(
            pika.URLParameters(rabbitmq_url)
        )
        self.channel = self.connection.channel()
        self.queue_name = queue_name
    
    def consume(self, callback: Callable) -> None:
        self.channel.queue_declare(queue=self.queue_name, durable=True)
        
        def wrapped_callback(ch, method, properties, body):
            try:
                message = json.loads(body)
                callback(message)
                ch.basic_ack(delivery_tag=method.delivery_tag)
            except Exception as e:
                # Отправить в Dead Letter Queue
                ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
        
        self.channel.basic_consume(
            queue=self.queue_name,
            on_message_callback=wrapped_callback
        )
        self.channel.start_consuming()

# Пример использования
publisher = EventPublisher('amqp://guest:guest@localhost/')
publisher.publish(
    exchange='user.events',
    routing_key='user.created',
    message={'user_id': 123, 'email': 'user@example.com'}
)

Kafka (потоки событий):

  • Масштабируемость для высоконагруженных систем
  • Репроцессинг (перечитать события с начала)
  • Естественно подходит для Event Sourcing
from kafka import KafkaProducer, KafkaConsumer
import json

# Producer
producer = KafkaProducer(
    bootstrap_servers=['localhost:9092'],
    value_serializer=lambda v: json.dumps(v).encode('utf-8')
)

producer.send('user-events', {
    'event': 'user.registered',
    'user_id': 123,
    'timestamp': '2025-03-22T10:00:00Z'
})

# Consumer
consumer = KafkaConsumer(
    'user-events',
    bootstrap_servers=['localhost:9092'],
    value_deserializer=lambda m: json.loads(m.decode('utf-8')),
    auto_offset_reset='earliest',
    group_id='notification-service'
)

for message in consumer:
    print(f"Обработка: {message.value}")

3. gRPC для высокопроизводительной коммуникации

Когда нужна максимальная производительность (микросекундные задержки):

// user.proto
syntax = "proto3";

service UserService {
    rpc GetUser (GetUserRequest) returns (User);
}

message GetUserRequest {
    int32 user_id = 1;
}

message User {
    int32 id = 1;
    string name = 2;
    string email = 3;
}
# user_pb2_grpc.py (сгенерировано из proto)
from concurrent import futures
import grpc

class UserServicer:
    def GetUser(self, request, context):
        # Получить пользователя из БД
        return user_pb2.User(
            id=request.user_id,
            name="Alice",
            email="alice@example.com"
        )

server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
user_pb2_grpc.add_UserServiceServicer_to_server(UserServicer(), server)
server.add_insecure_port('[::]:50051')
server.start()

4. GraphQL для flexible API

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

import strawberry
from typing import List

@strawberry.type
class User:
    id: int
    name: str
    email: str

@strawberry.type
class Query:
    @strawberry.field
    def user(self, id: int) -> User:
        # Получить пользователя
        return User(id=id, name="Alice", email="alice@example.com")
    
    @strawberry.field
    def users(self) -> List[User]:
        # Получить всех пользователей
        return [User(id=1, name="Alice", email="alice@example.com")]

schema = strawberry.Schema(query=Query)

Моя рекомендация: гибридный подход

┌─────────────────────────────────────────┐
│          API Gateway (REST)             │
│     (точка входа для клиентов)          │
└──────────────┬──────────────────────────┘
               │
     ┌─────────┴──────────┬──────────┐
     │                    │          │
  REST+              Message Queue  gRPC
Retry+Timeout       (Events)      (Internal)
     │                    │          │
     ▼                    ▼          ▼
[User Service]  [Notification]  [Cache]
[Product]       [Analytics]     [Search]
[Order]         [Audit Log]

Правила выбора:

  1. REST — для внешних API и простых синхронных операций
  2. Message Queue (RabbitMQ) — для надёжной асинхронной доставки событий
  3. Kafka — для высоконагруженных систем, требующих репроцессинга
  4. gRPC — для внутренней коммуникации сервисов, требующей максимальной производительности
  5. GraphQL — для клиентских приложений, требующих гибкости в запросах

Что важно помнить

  • Resilience: использовать timeout, retry, circuit breaker (библиотека tenacity)
  • Observability: логирование, метрики, трейсинг (OpenTelemetry, Jaeger)
  • Transactional Outbox Pattern — гарантировать доставку событий при отправке
  • Idempotency: сообщения должны быть идемпотентными (без дублирования эффекта)

Выбор подхода зависит от требований проекта, но комбинация REST + RabbitMQ + gRPC покрывает 90% реальных случаев.