← Назад к вопросам
Когда нужно использовать защищённые данные с точки зрения архитектуры?
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 (всё так или иначе доступно)