← Назад к вопросам
Как обеспечить надёжность с точки зрения качества компонентов?
2.0 Middle🔥 231 комментариев
#Архитектура и паттерны#Тестирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Надёжность компонентов: архитектура и качество
Надёжность — это не просто отсутствие ошибок, это предсказуемость, отказоустойчивость и поддерживаемость. Строится на основе хорошей архитектуры и практик.
1. Слоистая архитектура (Layered Architecture)
# ❌ Плохо: смешанные ответственности
def process_order(order_id):
# Доступ к БД
order = db.query(Order).get(order_id)
# Валидация
if order.total < 0:
return {'error': 'Invalid amount'}
# Бизнес-логика
order.status = 'processing'
if order.total > 1000:
order.is_premium = True
# Отправка email
send_email(order.customer.email, f'Order {order_id}')
# Сохранение
db.commit()
# Логирование
print(f'Order {order_id} processed')
return {'order_id': order_id, 'status': 'processing'}
# ✅ Хорошо: слои с четкими границами
# presentation/ → application/ → domain/ → infrastructure/
Presentation Layer (API, CLI)
↓ (вызывает)
Application Layer (Use Cases)
↓ (использует)
Domain Layer (Business Logic, Entities)
↓ (использует)
Infrastructure Layer (DB, Email, Cache)
⚠️ Зависимости ТОЛЬКО вниз, никогда вверх!
Структура проекта
app/
├── domain/ # Бизнес-логика, независима от всего
│ ├── entities/
│ │ └── order.py # Order бизнес-сущность
│ ├── value_objects/
│ │ └── money.py # Money VO
│ ├── services/ # Domain Services
│ │ └── order_service.py
│ └── exceptions/
│ └── order_exceptions.py
├── application/ # Use Cases, оркестрация
│ ├── use_cases/
│ │ └── process_order_use_case.py
│ └── dtos/
│ └── order_dto.py
├── infrastructure/ # Реализация (БД, email и т.д.)
│ ├── repositories/
│ │ └── order_repository.py
│ ├── services/
│ │ └── email_service.py
│ └── adapters/
│ └── payment_adapter.py
└── presentation/ # API endpoints, CLI
└── routes/
└── orders.py
Пример: Order Processing
# app/domain/entities/order.py
from dataclasses import dataclass
from decimal import Decimal
from datetime import datetime
from enum import Enum
class OrderStatus(Enum):
PENDING = 'pending'
PROCESSING = 'processing'
COMPLETED = 'completed'
FAILED = 'failed'
@dataclass
class Order:
"""Бизнес-сущность Order (независима от инфраструктуры)"""
id: int
customer_id: int
amount: Decimal
status: OrderStatus
created_at: datetime
def can_be_processed(self) -> bool:
"""Бизнес-правило: может ли заказ быть обработан"""
return self.status == OrderStatus.PENDING and self.amount > 0
def mark_as_processing(self):
"""Переход в статус processing"""
if not self.can_be_processed():
raise ValueError('Order cannot be processed')
self.status = OrderStatus.PROCESSING
def mark_as_completed(self):
"""Переход в статус completed"""
self.status = OrderStatus.COMPLETED
# app/domain/services/order_service.py
class OrderDomainService:
"""Domain service для бизнес-логики заказов"""
def is_premium_order(self, order: Order) -> bool:
"""Бизнес-правило: это премиум заказ?"""
return order.amount >= Decimal('1000')
def calculate_processing_time(self, order: Order) -> int:
"""Сколько часов обрабатывать заказ"""
if self.is_premium_order(order):
return 1 # Премиум обрабатывается быстрее
return 24
# app/application/use_cases/process_order_use_case.py
from app.domain.entities import Order, OrderStatus
from app.infrastructure.repositories import OrderRepository
from app.infrastructure.services import EmailService, PaymentService
from app.domain.services import OrderDomainService
import logging
logger = logging.getLogger(__name__)
class ProcessOrderUseCase:
"""Use Case: обработать заказ"""
def __init__(
self,
order_repository: OrderRepository,
email_service: EmailService,
payment_service: PaymentService
):
self.order_repository = order_repository
self.email_service = email_service
self.payment_service = payment_service
self.domain_service = OrderDomainService()
def execute(self, order_id: int) -> dict:
"""Выполнить обработку заказа"""
try:
# 1. Получить заказ
order = self.order_repository.get(order_id)
if not order:
raise ValueError(f'Order {order_id} not found')
# 2. Валидация бизнес-правил (domain layer)
if not order.can_be_processed():
raise ValueError(f'Order cannot be processed. Status: {order.status}')
# 3. Изменение состояния (domain layer)
order.mark_as_processing()
self.order_repository.update(order)
# 4. Отправка письма
is_premium = self.domain_service.is_premium_order(order)
self.email_service.send_processing_notification(
order.customer_id,
order.id,
is_premium
)
# 5. Обработка платежа
try:
self.payment_service.process_payment(order.id, order.amount)
except Exception as e:
logger.error(f'Payment failed for order {order_id}: {e}')
order.status = OrderStatus.FAILED
self.order_repository.update(order)
raise
# 6. Завершение
order.mark_as_completed()
self.order_repository.update(order)
logger.info(f'Order {order_id} processed successfully')
return {
'order_id': order_id,
'status': order.status.value,
'amount': str(order.amount)
}
except ValueError as e:
logger.warning(f'Validation error for order {order_id}: {e}')
raise
except Exception as e:
logger.error(f'Unexpected error processing order {order_id}: {e}')
raise
# app/presentation/routes/orders.py
from fastapi import APIRouter, HTTPException
from app.application.use_cases import ProcessOrderUseCase
from app.infrastructure.repositories import OrderRepository
from app.infrastructure.services import EmailService, PaymentService
router = APIRouter(prefix='/orders', tags=['orders'])
@router.post('/{order_id}/process')
async def process_order(order_id: int):
"""API endpoint для обработки заказа"""
try:
# Инъекция зависимостей
use_case = ProcessOrderUseCase(
order_repository=OrderRepository(db_session),
email_service=EmailService(),
payment_service=PaymentService()
)
# Выполнение use case
result = use_case.execute(order_id)
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail='Internal server error')
2. SOLID принципы
Single Responsibility Principle (SRP)
# ❌ Плохо: класс делает слишком много
class UserService:
def register(self, email, password):
# Валидация
if not email:
raise ValueError('Email required')
# Сохранение в БД
user = User(email=email, password=hash_password(password))
db.add(user)
# Отправка email
send_email(email, 'Welcome')
# Логирование
log(f'User {email} registered')
return user
# ✅ Хорошо: каждый класс одну ответственность
class EmailValidator:
def validate(self, email: str) -> bool:
return '@' in email and '.' in email
class PasswordHasher:
def hash(self, password: str) -> str:
return bcrypt.hashpw(password.encode(), bcrypt.gensalt())
class UserRepository:
def create(self, email: str, password_hash: str) -> User:
user = User(email=email, password_hash=password_hash)
self.session.add(user)
self.session.commit()
return user
class EmailService:
def send_welcome(self, email: str):
# Отправка письма
pass
class UserRegistrationUseCase:
def __init__(self, validator, hasher, repository, email_service, logger):
self.validator = validator
self.hasher = hasher
self.repository = repository
self.email_service = email_service
self.logger = logger
def register(self, email: str, password: str) -> User:
if not self.validator.validate(email):
raise ValueError('Invalid email')
password_hash = self.hasher.hash(password)
user = self.repository.create(email, password_hash)
try:
self.email_service.send_welcome(email)
except Exception as e:
self.logger.error(f'Failed to send welcome email: {e}')
self.logger.info(f'User {email} registered')
return user
Open/Closed Principle (OCP)
# ❌ Плохо: нужно менять класс при добавлении новой БД
class UserRepository:
def save(self, user):
if config.db_type == 'postgresql':
# PostgreSQL code
pass
elif config.db_type == 'mysql':
# MySQL code
pass
elif config.db_type == 'mongodb':
# MongoDB code
pass
# ✅ Хорошо: используй абстракцию
from abc import ABC, abstractmethod
class UserRepository(ABC):
@abstractmethod
def save(self, user) -> None:
pass
class PostgreSQLUserRepository(UserRepository):
def save(self, user):
# PostgreSQL реализация
pass
class MongoDBUserRepository(UserRepository):
def save(self, user):
# MongoDB реализация
pass
# Использование (открыто для расширения, закрыто для изменения)
def create_repository() -> UserRepository:
if config.db_type == 'postgresql':
return PostgreSQLUserRepository()
elif config.db_type == 'mongodb':
return MongoDBUserRepository()
3. Обработка ошибок
# ❌ Плохо: логируешь и бросаешь исключение (double logging)
def process_order(order_id):
try:
result = get_order(order_id)
except OrderNotFound as e:
logger.error(f'Order not found: {e}') # Логирование в service
raise
# На уровне handler
@router.get('/orders/{order_id}')
async def get_order_handler(order_id: int):
try:
return process_order(order_id)
except OrderNotFound as e:
logger.error(f'Order not found: {e}') # ЕЩЕ раз логирование!
raise HTTPException(status_code=404)
# ✅ Правильно: логируй один раз на границе
class OrderRepository:
def get(self, order_id: int) -> Order:
"""Не логируем — просто бросаем исключение"""
order = self.session.query(Order).get(order_id)
if not order:
raise OrderNotFound(order_id)
return order
# На уровне handler (граница системы)
@router.get('/orders/{order_id}')
async def get_order_handler(order_id: int):
try:
return get_order_service.execute(order_id)
except OrderNotFound as e:
logger.warning(f'Order not found: {e}') # Логируем один раз
raise HTTPException(status_code=404)
except Exception as e:
logger.error(f'Unexpected error: {e}') # Перехватываем неожиданное
raise HTTPException(status_code=500)
4. Тестирование
# tests/unit/application/test_process_order_use_case.py
import pytest
from unittest.mock import Mock
from app.application.use_cases import ProcessOrderUseCase
from app.domain.entities import Order, OrderStatus
class TestProcessOrderUseCase:
@pytest.fixture
def mock_dependencies(self):
return {
'order_repository': Mock(),
'email_service': Mock(),
'payment_service': Mock()
}
@pytest.fixture
def use_case(self, mock_dependencies):
return ProcessOrderUseCase(**mock_dependencies)
def test_process_successful_order(self, use_case, mock_dependencies):
# Arrange
order = Order(
id=1,
customer_id=1,
amount=Decimal('100'),
status=OrderStatus.PENDING,
created_at=datetime.now()
)
mock_dependencies['order_repository'].get.return_value = order
# Act
result = use_case.execute(1)
# Assert
assert result['status'] == 'completed'
mock_dependencies['payment_service'].process_payment.assert_called_once()
mock_dependencies['email_service'].send_processing_notification.assert_called_once()
5. Метрики качества
# Test Coverage
make coverage
# Цель: > 90%
# Code Quality (Linting)
make lint
# Используй ruff, pylint, flake8
# Type Checking
mypy app/
# Строгая типизация
# Complexity
radon cc app/ -a
# Циклическая сложность < 10
# Code Duplication
radon mi app/ -m
# Maintainability Index > 80
6. Лучшие практики
- Слои архитектуры — четкие границы
- SOLID принципы — гибкий и масштабируемый код
- DRY, KISS — простота и переиспользование
- Тестирование — 90%+ coverage
- Обработка ошибок — логируй один раз на границе
- Dependency Injection — легко мокировать
- Code Review — проверка качества
- Документация — объясни сложные части
Надёжность строится на фундаменте хорошей архитектуры и дисциплины!