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

Чем плоха зависимость бизнес-логики от низкоуровневой реализации?

2.2 Middle🔥 201 комментариев
#Python Core

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

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

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

Почему бизнес-логика не должна зависеть от низкоуровневой реализации

Это фундаментальный принцип чистой архитектуры. Зависимость бизнес-логики от реализационных деталей создаёт множество проблем. Разберу подробно.

Проблема: прямая зависимость

Пример 1: Плохо — бизнес-логика привязана к БД

# Плохо: бизнес-логика знает про SQLAlchemy и PostgreSQL
from sqlalchemy import create_engine
from sqlalchemy.orm import Session

class OrderService:
    def __init__(self):
        self.engine = create_engine('postgresql://...')
    
    def process_order(self, order_data):
        # Бизнес-логика перемешана с работой с БД
        session = Session(self.engine)
        try:
            order = Order(
                user_id=order_data['user_id'],
                total=order_data['total']
            )
            session.add(order)
            session.commit()
            return order.id
        except SQLAlchemyError as e:
            session.rollback()
            raise

# Проблемы:
# 1. Нельзя протестировать без БД
# 2. Нельзя поменять БД (PostgreSQL → MongoDB)
# 3. Нельзя использовать другой ORM
# 4. Сложно добавить логирование или другие сервисы

Пример 2: Плохо — зависит от HTTP клиента

import requests

class RecommendationService:
    def get_recommendations(self, user_id):
        # Прямой HTTP запрос в бизнес-логике
        response = requests.get(
            f'https://ml-service.com/api/recommendations/{user_id}',
            timeout=5
        )
        if response.status_code == 200:
            return response.json()['items']
        else:
            return []

# Проблемы:
# 1. Зависит от requests библиотеки
# 2. Зависит от конкретного URL
# 3. Зависит от конкретного API формата
# 4. Сложно тестировать
# 5. Если ML сервис недоступен — падает бизнес-логика

Почему это плохо: 5 главных проблем

Проблема 1: Сложность тестирования

# Невозможно легко тестировать бизнес-логику
def test_process_order():
    service = OrderService()
    # Нужна реальная БД! Или придется мокировать SQLAlchemy
    # Тесты становятся хрупкими и медленными
    result = service.process_order({'user_id': 1, 'total': 100})
    assert result is not None

# Вместо того чтобы тестировать логику, мы тестируем интеграцию

Проблема 2: Невозможность менять реализацию

# Захотели поменять PostgreSQL на MongoDB?
# Придется переписывать весь OrderService

# Захотели использовать Redis вместо SQLAlchemy?
# Переписываем ещё раз

# Каждый раз риск сломать бизнес-логику

Проблема 3: Усложнение зависимостей

# Если бизнес-логика знает про SQLAlchemy, то:
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from sqlalchemy.exc import SQLAlchemyError
# ... и это только для БД

# Плюс HTTP клиент, плюс кэширование, плюс логирование
# Результат: монолитный, нечитаемый код

Проблема 4: Сложность в командной работе

# Если бизнес-логика привязана к PostgreSQL:
# - Frontend разработчик не может запустить сервис без БД
# - Новый разработчик потребует много времени на setup
# - CI/CD пайплайн усложняется
# - Контейнеризация требует всего стека

Проблема 5: Нарушение принципов SOLID

# Зависимость обращена в неправильную сторону
# Должно быть:
# бизнес-логика -> абстракция <- реализация

# Получается:
# бизнес-логика -> конкретная реализация
# Нарушены принципы:
# - DIP (Dependency Inversion) 
# - OCP (Open/Closed)
# - SRP (Single Responsibility)

Решение: Dependency Injection и абстракции

Хорошее решение 1: Используем репозитории

# Определяем абстракцию (интерфейс)
from abc import ABC, abstractmethod

class OrderRepository(ABC):
    @abstractmethod
    def save(self, order) -> int:
        """Сохранить заказ и вернуть ID"""
        pass
    
    @abstractmethod
    def get_by_id(self, order_id: int):
        """Получить заказ по ID"""
        pass

# Конкретная реализация для PostgreSQL
class PostgreSQLOrderRepository(OrderRepository):
    def __init__(self, session: Session):
        self.session = session
    
    def save(self, order) -> int:
        self.session.add(order)
        self.session.commit()
        return order.id
    
    def get_by_id(self, order_id: int):
        return self.session.query(Order).filter_by(id=order_id).first()

# Другая реализация для MongoDB
class MongoDBOrderRepository(OrderRepository):
    def __init__(self, db):
        self.db = db
    
    def save(self, order) -> int:
        result = self.db.orders.insert_one(order.to_dict())
        return result.inserted_id
    
    def get_by_id(self, order_id: int):
        return self.db.orders.find_one({'_id': order_id})

# Бизнес-логика: зависит только от абстракции
class OrderService:
    def __init__(self, repository: OrderRepository):
        # Dependency Injection - получаем абстракцию
        self.repository = repository
    
    def process_order(self, order_data) -> int:
        # Чистая бизнес-логика, не знает про БД
        order = Order(
            user_id=order_data['user_id'],
            total=order_data['total'],
            status='pending'
        )
        return self.repository.save(order)

# Преимущества:
# - Бизнес-логика не знает про конкретную БД
# - Легко менять PostgreSQL на MongoDB
# - Легко тестировать с mock репозиторием
# - Код чистый и понятный

Тестирование стало простым

# Mock репозиторий для тестирования
class MockOrderRepository(OrderRepository):
    def __init__(self):
        self.orders = {}
        self.next_id = 1
    
    def save(self, order) -> int:
        order_id = self.next_id
        self.orders[order_id] = order
        self.next_id += 1
        return order_id
    
    def get_by_id(self, order_id: int):
        return self.orders.get(order_id)

# Тест становится простым и быстрым
def test_process_order():
    repo = MockOrderRepository()
    service = OrderService(repo)
    
    order_id = service.process_order({'user_id': 1, 'total': 100})
    
    assert order_id == 1
    assert repo.orders[1].total == 100
    # Нет зависимости от БД, нет медленных тестов

Хорошее решение 2: Используем абстракции для внешних сервисов

# Абстракция для рекомендаций
from abc import ABC, abstractmethod

class RecommendationProvider(ABC):
    @abstractmethod
    def get_recommendations(self, user_id: int) -> list:
        pass

# Реализация через HTTP
class MLServiceRecommendationProvider(RecommendationProvider):
    def __init__(self, base_url: str, timeout: int = 5):
        self.base_url = base_url
        self.timeout = timeout
    
    def get_recommendations(self, user_id: int) -> list:
        response = requests.get(
            f'{self.base_url}/recommendations/{user_id}',
            timeout=self.timeout
        )
        if response.status_code == 200:
            return response.json()['items']
        return []

# Альтернативная реализация через кэш
class CachedRecommendationProvider(RecommendationProvider):
    def __init__(self, provider: RecommendationProvider, cache):
        self.provider = provider
        self.cache = cache
    
    def get_recommendations(self, user_id: int) -> list:
        cache_key = f'recommendations:{user_id}'
        cached = self.cache.get(cache_key)
        if cached:
            return cached
        
        recommendations = self.provider.get_recommendations(user_id)
        self.cache.set(cache_key, recommendations, ttl=3600)
        return recommendations

# Бизнес-логика
class UserService:
    def __init__(self, provider: RecommendationProvider):
        self.provider = provider
    
    def get_user_feed(self, user_id: int) -> dict:
        recommendations = self.provider.get_recommendations(user_id)
        # Чистая бизнес-логика
        return {
            'user_id': user_id,
            'recommendations': recommendations,
            'count': len(recommendations)
        }

Правильная архитектура: слои

# Слои должны быть так расположены:
# 
# Presentation (HTTP, CLI, WebSocket)
#       ↓
# Application (Бизнес-сценарии)
#       ↓
# Domain (Чистая бизнес-логика)
#       ↑
# Infrastructure (БД, HTTP, кэши)
#
# Зависимости идут ВНИЗ к абстракциям,
# а не в сторону конкретных реализаций

# Domain слой НЕ знает про:
# - SQLAlchemy
# - Requests
# - Redis
# - FastAPI
# Только про абстракции!

Практический пример: полная структура

# domain/models/order.py
class Order:
    def __init__(self, user_id: int, total: float):
        self.user_id = user_id
        self.total = total
        self.status = 'pending'
    
    def approve(self):
        self.status = 'approved'
    
    def cancel(self):
        self.status = 'cancelled'

# domain/repositories.py
from abc import ABC, abstractmethod

class OrderRepository(ABC):
    @abstractmethod
    def save(self, order: Order) -> int:
        pass

# application/services/order_service.py
class OrderService:
    def __init__(self, repository: OrderRepository):
        self.repository = repository
    
    def process_order(self, user_id: int, total: float) -> int:
        order = Order(user_id, total)
        order.approve()
        return self.repository.save(order)

# infrastructure/repositories/postgres_order_repository.py
from sqlalchemy.orm import Session

class PostgreSQLOrderRepository(OrderRepository):
    def __init__(self, session: Session):
        self.session = session
    
    def save(self, order: Order) -> int:
        db_order = OrderModel(**order.__dict__)
        self.session.add(db_order)
        self.session.commit()
        return db_order.id

# presentation/api/endpoints.py
from fastapi import APIRouter, Depends

router = APIRouter()

@router.post('/orders')
def create_order(order_data: dict, service: OrderService = Depends()):
    order_id = service.process_order(
        user_id=order_data['user_id'],
        total=order_data['total']
    )
    return {'order_id': order_id}

Заключение

Главное правило: Зависимости должны быть ОБРАЩЕНЫ к абстракциям, а не к конкретным реализациям.

Когда бизнес-логика не зависит от деталей реализации:

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

Это основа чистой архитектуры и SOLID принципов.

Чем плоха зависимость бизнес-логики от низкоуровневой реализации? | PrepBro