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

Когда нужно использовать защищённые данные с точки зрения архитектуры?

2.8 Senior🔥 101 комментариев
#Архитектура и паттерны#Безопасность

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

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

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

Защищённые данные в архитектуре

Защищённые данные (private fields, encapsulation) — это фундаментальный принцип объектно-ориентированного программирования и clean architecture. Вопрос не только в технике, но и в архитектурном дизайне.

Когда использовать защищённые данные

1. Инкапсуляция состояния (объектные модели)

# ❌ Плохо - открытые данные
class BankAccount:
    def __init__(self, balance):
        self.balance = balance  # Открыто!

account = BankAccount(1000)
account.balance = -999999  # Кто угодно может испортить данные!

# ✅ Хорошо - защищённые данные
class BankAccount:
    def __init__(self, balance: float):
        self._balance = balance  # Защищено
    
    @property
    def balance(self) -> float:
        """Только чтение баланса"""
        return self._balance
    
    def withdraw(self, amount: float) -> None:
        """Снятие с валидацией"""
        if amount > self._balance:
            raise ValueError("Insufficient funds")
        self._balance -= amount
    
    def deposit(self, amount: float) -> None:
        """Пополнение счёта"""
        if amount <= 0:
            raise ValueError("Amount must be positive")
        self._balance += amount

account = BankAccount(1000)
account.deposit(500)  # ✅ Безопасно
account._balance = -999999  # ⚠️ Технически можно, но мы игнорируем private API

2. Защита инвариантов (Domain-Driven Design)

# Domain Entity с инвариантами
class User:
    def __init__(self, user_id: int, email: str, status: str):
        self._id = user_id
        self._email = email
        self._status = status  # Только определённые значения
        self._failed_logins = 0
        self._locked_at = None
    
    @property
    def email(self) -> str:
        return self._email
    
    @property
    def status(self) -> str:
        return self._status
    
    def record_failed_login(self) -> None:
        """Логирует неудачный вход. При 3+ попытках блокирует."""
        self._failed_logins += 1
        if self._failed_logins >= 3:
            self._status = "locked"
            self._locked_at = datetime.now(UTC)
    
    def unlock(self) -> None:
        """Разблокировка"""
        if self._status == "locked":
            self._status = "active"
            self._failed_logins = 0
            self._locked_at = None

3. Скрытие деталей реализации (Interface segregation)

# Клиент видит только интерфейс, не деталь
class UserRepository:
    def __init__(self, db: AsyncSession):
        self._db = db  # Деталь - БД сессия
        self._cache = {}  # Деталь - кэш
    
    async def get_by_id(self, user_id: int) -> Optional[User]:
        # Клиент не знает, что используется кэш + БД
        if user_id in self._cache:
            return self._cache[user_id]
        
        stmt = select(User).where(User.id == user_id)
        result = await self._db.execute(stmt)
        user = result.scalar_one_or_none()
        
        if user:
            self._cache[user_id] = user
        
        return user

# Фасад
class UserService:
    def __init__(self, repo: UserRepository):
        self._repo = repo
    
    async def get_user(self, user_id: int) -> dict:
        user = await self._repo.get_by_id(user_id)
        if not user:
            raise UserNotFoundError(user_id)
        return {
            "id": user.id,
            "email": user.email,
            # Скрываем внутренние детали
        }

4. Предотвращение нежелательного использования

# ❌ Публичный класс - любой может использовать неправильно
class PaymentProcessor:
    def __init__(self):
        self.transaction_log = []  # Открыто!
    
    def process(self, amount: float):
        # Кто-то может изменить transaction_log
        self.transaction_log.clear()  # Ошибка аудита!
        return {"status": "success"}

# ✅ Защищённый класс
class PaymentProcessor:
    def __init__(self):
        self._transaction_log = []  # Защищено
    
    def process(self, amount: float) -> dict:
        transaction = {"amount": amount, "timestamp": datetime.now(UTC)}
        self._transaction_log.append(transaction)
        return {"status": "success", "id": transaction["id"]}
    
    def get_transactions(self) -> list:
        """Только чтение истории"""
        return list(self._transaction_log)  # Копия, не оригинал

Архитектурные слои и защита

Domain Layer

# Модели Domain должны защищать инварианты
class Order:
    def __init__(self, order_id: int):
        self._id = order_id
        self._items: list[OrderItem] = []
        self._status = OrderStatus.PENDING
        self._total = 0.0
    
    def add_item(self, item: OrderItem) -> None:
        """Добавить товар в заказ"""
        if self._status != OrderStatus.PENDING:
            raise InvalidStateError("Cannot add items to completed order")
        self._items.append(item)
        self._total += item.price * item.quantity
    
    def confirm(self) -> None:
        """Подтвердить заказ"""
        if not self._items:
            raise InvalidOrderError("Cannot confirm empty order")
        self._status = OrderStatus.CONFIRMED

Application Layer

# Service использует domain модели
class CreateOrderUseCase:
    def __init__(self, repo: OrderRepository, payment: PaymentService):
        self._repo = repo  # Защищено
        self._payment = payment  # Защищено
    
    async def execute(self, user_id: int, items: list) -> Order:
        # Создаём domain объект
        order = Order(order_id=generate_id())
        
        for item_data in items:
            item = OrderItem(**item_data)
            order.add_item(item)  # Инварианты проверяются здесь
        
        order.confirm()
        
        # Сохраняем
        await self._repo.save(order)
        
        return order

Infrastructure Layer

# Repository скрывает деталь хранилища
class SQLAlchemyOrderRepository:
    def __init__(self, session: AsyncSession):
        self._session = session  # Защищено
    
    async def save(self, order: Order) -> None:
        """Сохранить в БД"""
        # Преобразуем domain модель в ORM модель
        orm_order = OrderORM(
            id=order.id,
            status=order.status.value,
            total=order.total
        )
        self._session.add(orm_order)
        await self._session.commit()

Правило: защищай то, что может сломаться

# ✅ Это нужно защищать
class MutableObject:
    def __init__(self):
        self._state = []  # Состояние, которое может измениться
        self._config = {}  # Конфигурация, которую нужно валидировать

# ✅ Это тоже нужно защищать
class DatabaseConnection:
    def __init__(self, url: str):
        self._url = url  # Чувствительные данные
        self._connection = None  # Ресурс, требующий управления

# ❌ Это не нужно защищать (неизменяемое, не имеет инвариантов)
class DataContainer:
    def __init__(self, data: dict):
        self.data = data  # Просто контейнер, можно открыть

Python соглашения

# _name — protected ("просим не трогать")
class Parent:
    def __init__(self):
        self._protected = "Не трогай!"  # Можно переопределить в подклассе

# __name — private (name mangling)
class Private:
    def __init__(self):
        self.__private = "Не трогай!"  # Переименуется в _Private__private

# В большинстве случаев используют _name, т.к. __name слишком жёсткий

Когда НЕ защищать

# ❌ DTO/Pydantic модели - обычно открыты
class UserDTO(BaseModel):
    id: int
    name: str
    email: str

# ❌ Простые структуры данных
class Config:
    def __init__(self):
        self.debug = True  # Открыто - это просто конфиг
        self.timeout = 30

Итого

Используй защищённые данные когда:

  • Есть инварианты (состояние, которое должно быть валидным)
  • Есть деталь реализации (БД, кэш, сеть)
  • Требуется контроль доступа (только чтение, только запись)
  • Domain Entity (объект с поведением и состоянием)

Не защищай когда:

  • Простой контейнер данных (DTO, Pydantic модели)
  • Нет инвариантов (просто структура данных)
  • Публичный API (всё так или иначе доступно)