> Что является верхнеуровневым и низкоуровневым компонентом в системах на SOLID? (Python)

Уровень: senior · Роль: backend · Язык: Python · Категория: Технические вопросы

Компании: HeadHunter

Стек: Python

> Пример ответа

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

Верхнеуровневый компонент - это бизнес-логика и политики приложения, которые не зависят от деталей реализации. Низкоуровневый компонент - это конкретные механизмы: базы данных, HTTP-клиенты, файловые системы, сторонние библиотеки. Принцип инверсии зависимостей (D в SOLID) требует, чтобы оба уровня зависели от абстракций, а не друг от друга напрямую. В Python это реализуется через протоколы, ABC или типизированные интерфейсы.

Подробное объяснение

В контексте SOLID верхнеуровневые компоненты - это модули, которые содержат бизнес-правила и сценарии использования. Они определяют, что система делает, а не как. Низкоуровневые компоненты - это адаптеры, драйверы и утилиты, которые обеспечивают взаимодействие с внешним миром: ORM, репозитории, API-клиенты, кэши.

Ключевой момент: в классической архитектуре верхний уровень зависит от нижнего, но в SOLID эта зависимость разворачивается. Оба уровня должны зависеть от абстракций (интерфейсов). Верхнеуровневый компонент определяет интерфейс, который ему нужен, а низкоуровневый реализует этот интерфейс. Это позволяет заменять реализации без изменения бизнес-логики.

Пример: сервис заказов (верхний уровень) не должен знать про конкретную БД. Он объявляет интерфейс OrderRepository с методами save() и find(). Конкретный PostgresOrderRepository (нижний уровень) реализует этот интерфейс. Сервис работает только с абстракцией.

На практике

В Python на практике это выглядит так:

  • Используйте abc.ABC или typing.Protocol для определения интерфейсов.
  • Внедряйте зависимости через конструктор, а не создавайте их внутри класса.
  • Для сложных графов зависимостей используйте DI-контейнеры (например, dependency-injector), но для большинства случаев достаточно простого ручного внедрения.
  • Следите, чтобы импорты шли от абстракций к реализациям, а не наоборот.
  • В тестах подменяйте низкоуровневые компоненты фейками или моками, реализующими тот же интерфейс.

Типичная структура проекта: domain/ (верхний уровень), infrastructure/ или adapters/ (нижний уровень), application/ (use cases).

Пример кода

PYTHON
from abc import ABC, abstractmethod
from dataclasses import dataclass
# Верхнеуровневый компонент: определяет интерфейс
class OrderRepository(ABC):
@abstractmethod
def save(self, order: "Order") -> None: ...
@abstractmethod
def find_by_id(self, order_id: int) -> "Order | None": ...
# Верхнеуровневый компонент: бизнес-логика
@dataclass
class Order:
id: int
total: float
class OrderService:
def __init__(self, repo: OrderRepository):
self._repo = repo
def create_order(self, order_id: int, total: float) -> Order:
order = Order(id=order_id, total=total)
self._repo.save(order)
return order
# Низкоуровневый компонент: реализация для конкретной БД
class PostgresOrderRepository(OrderRepository):
def save(self, order: Order) -> None:
# SQL-запрос к postgres
print(f"INSERT INTO orders VALUES ({order.id}, {order.total})")
def find_by_id(self, order_id: int) -> Order | None:
# SELECT ...
return Order(id=order_id, total=100.0)
# Использование
repo = PostgresOrderRepository()
service = OrderService(repo)
service.create_order(1, 250.0)

Как отвечать на собеседовании

Начните с чёткого определения: верхний уровень - бизнес-правила, нижний - инфраструктура. Подчеркните, что главное - это направление зависимостей: они должны указывать на абстракции, а не на конкретику. Приведите пример из своего опыта: как вы отделяли слой работы с БД от сервисного слоя. Упомяните, что в Python это часто реализуется через ABC или Protocol, и что тестируемость - главный выигрыш от такого подхода.

Если спросят про "чистую архитектуру" или "гексагональную" - свяжите с тем же принципом: ядро приложения не знает про внешние адаптеры.

Что проверяет интервьюер

Интервьюер хочет убедиться, что вы:

  • Понимаете суть принципа инверсии зависимостей, а не просто знаете формулировку.
  • Можете отличить бизнес-логику от инфраструктурного кода.
  • Умеете проектировать интерфейсы и внедрять зависимости.
  • Понимаете, как это влияет на тестируемость и поддерживаемость.
  • Знаете, как это реализуется в Python (ABC, Protocol, DI).

Типичные ошибки

  • Путаница: называют верхнеуровневым компонентом тот, что "выше по стеку" (например, контроллеры). Это неверно - контроллеры часто относятся к инфраструктуре.
  • Говорят, что верхний уровень "не должен ничего знать о нижнем" - это верно, но недостаточно: важно, что нижний тоже не знает о верхнем, оба зависят от абстракции.
  • Предлагают использовать наследование от конкретного класса как способ инверсии - это не работает, нужен интерфейс.
  • Забывают, что в Python интерфейсы не обязательны формально, но их отсутствие приводит к хрупкому коду.
  • Утверждают, что DI-контейнер обязателен - на самом деле для большинства проектов достаточно ручного внедрения.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью