Опишите ваш опыт в проектировании архитектуры бэкенд-приложений.

Ответ

Да, я неоднократно участвовал в проектировании и развитии архитектуры. В качестве примера могу привести работу над высоконагруженным проектом, где мы строили систему на основе микросервисов.

Ключевые принципы, которыми я руководствовался:

  • Разделение по бизнес-доменам (Domain-Driven Design): Каждый сервис отвечал за свою часть бизнес-логики (например, users, orders, payments).
  • Слабая связанность (Loose Coupling): Сервисы должны как можно меньше знать друг о друге, взаимодействуя через публичные контракты (API).
  • Высокая отказоустойчивость: Система должна продолжать работать даже при отказе одного или нескольких компонентов.

Принятые архитектурные решения и их обоснование:

  1. Синхронное взаимодействие: Использовали gRPC для межсервисных вызовов. Выбрали его из-за высокой производительности (бинарный протокол, HTTP/2) и строгой типизации контрактов с помощью .proto файлов.
  2. Асинхронное взаимодействие: Внедрили Apache Kafka для построения Event-Driven архитектуры. Это позволило нам расцепить сервисы: например, сервис заказов публиковал событие OrderCreated, а сервисы уведомлений и склада подписывались на него и реагировали независимо.
  3. Работа с данными: Использовали PostgreSQL с настроенной репликацией (Master-Slave). Запросы на чтение отправлялись на реплики для снижения нагрузки на основную базу данных.
  4. Отказоустойчивость: Применяли паттерны, такие как Circuit Breaker, чтобы избежать каскадных отказов, и компенсирующие транзакции для поддержания консистентности данных в распределенной системе.

Пример кода (сервис заказов):

type OrderService struct {
    repo          OrderRepository // Репозиторий для работы с БД
    eventProducer EventProducer   // Продюсер событий в Kafka
}

func (s *OrderService) CreateOrder(ctx context.Context, req *CreateOrderRequest) (*Order, error) {
    // 1. Создаем и сохраняем заказ в транзакции
    order, err := s.repo.Save(ctx, convertToDomain(req))
    if err != nil {
        return nil, fmt.Errorf("failed to save order: %w", err)
    }

    // 2. Публикуем событие о создании заказа
    event := NewOrderCreatedEvent(order)
    if err := s.eventProducer.Publish(ctx, event); err != nil {
        // Если не удалось отправить событие, откатываем создание заказа
        // Это пример компенсирующей транзакции
        s.repo.Delete(ctx, order.ID)
        return nil, fmt.Errorf("failed to publish event: %w", err)
    }

    return order, nil
}