Architectural Principles

Принципы проектирования систем

1. Component Principles

Как строить компоненты, чтобы ими было удобно управлять

🔹 Принцип заменяемости

Компонент должен быть заменяем без переписывания системы

class EmailSender:
    def send(self, msg): ...

# Можно заменить на SMS, Slack, etc.
class SMSSender:
    def send(self, msg): ...

🔹 Единая ответственность компонента

Один компонент — одна зона ответственности

auth-service/
  ├── login.py
  ├── register.py
  └── token.py

# Не смешиваем с payment или profile

🔹 Легко тестировать, развивать, развертывать

2. Policy vs Detail

Бизнес-логика не должна зависеть от деталей реализации

❌ Нарушение

class TrainingService:
    def __init__(self):
        self.db = PostgreSQL()  # жёсткая зависимость

    def train_model(self):
        data = self.db.get_data()
        # ... обучение ...
        self.db.save_result(result)

Что если поменяется БД?

✅ Исправлено: Policy наверху

class TrainingService:
    def __init__(self, data_source: DataSource, result_sink: ResultSink):
        self.source = source
        self.sink = sink

    def train_model(self):
        data = self.source.get_data()
        # ... обучение ...
        self.sink.save(result)

# Зависимости внедряются снаружи
service = TrainingService(PostgresSource(), RedisSink())

🔹 Политика (что делать) отделена от деталей (как)

3. Coupling & Cohesion

Связность и сцепление — два ключевых качества архитектуры

🔸 Low Coupling (слабое сцепление)

Компоненты зависят друг от друга минимально

# ❌ Сильное сцепление
user_service.db.engine.connection.cursor.execute(...)

# ✅ Слабое сцепление
user_repo.save(user)  # абстракция

🔹 Меняешь реализацию — не ломается всё

🔸 High Cohesion (высокая связанность внутри)

Всё, что относится к одной задаче — вместе

notification/
  ├── email_sender.py
  ├── sms_sender.py
  ├── templates/
  └── service.py

# Вместо:
# services/email.py
# utils/sms.py
# templates/notification/

🔹 Легко найти, понять, изменить

🎯 Хорошая архитектура =

Низкое сцепление + высокая связанность

Coupling vs Cohesion

4. Boundaries (Границы)

Где заканчивается одна часть и начинается другая

🔸 Границы между слоями

UI → Use Cases → Domain → Infrastructure
       ↑               ↑
   зависит от    зависит от

🔹 Код в Domain не знает про FastAPI или PostgreSQL

🔸 Границы между сервисами

# auth-service предоставляет API
POST /login → { "token": "..." }

# order-service использует его
response = requests.post("auth/login", json=creds)

🔹 Внутренняя реализация скрыта за границей

🔸 Границы в коде: модули и пакеты

# user/domain.py
class User: ...

# user/service.py
class UserService: ...

# user/repository.py
class UserRepository: ...

🔹 Явное разделение — меньше ошибок при изменениях

💡 Почему это важно?

  • Позволяет менять части системы независимо
  • Упрощает тестирование
  • Защищает от "эффекта домино"
  • Делает архитектуру понятной

🎯 Итог: 4 кита архитектуры

Принцип Суть
Component Principles Компоненты должны быть заменяемыми и чёткими
Policy vs Detail Бизнес — наверху, детали — снизу
Coupling & Cohesion Меньше связей между, больше внутри
Boundaries Чёткие границы — основа масштабируемости
"Хорошая архитектура — это не про технологии.
Это про управляемость, гибкость и долгую жизнь системы."

Architectural Thinking

Как принимать архитектурные решения

1. Ответственность компонента

Что должно быть вместе? Что — отдельно?

❌ Один сервис на всё

class UserService:
    def login(self): ...           # безопасность
    def update_profile(self): ...   # UI
    def send_email(self): ...       # интеграция
    def export_data(self): ...      # ETL

Пять причин для изменения → хрупкий код

✅ Разделяй по осям изменений

auth-service/
├── POST /login
└── JWT

profile-service/
├── GET /profile
└── PUT /avatar

notification-service/
├── send_email()
└── send_sms()

🔹 Каждый меняется независимо

2. Stability vs Volatility

Что стабильно? Что будет меняться?

🔸 Ядро системы — стабильно

class TrainingJob:
    def run(self, dataset: Dataset) -> Model:
        # бизнес-логика обучения
        pass

Редко меняется

🔸 Детали — volatile

class CSVDataset(Dataset): ...
class HDF5Dataset(Dataset): ...

class LocalTrainer(Trainer): ...
class CloudTrainer(Trainer): ...

Будут меняться: форматы, платформы, API

🎯 Вывод

Детали зависят от ядра,
а не наоборот.

→ Это и есть настоящая инверсия зависимостей

3. Ca vs Ce

Кто на кого зависит? Где ядро?

🔸 Afferent (Ca) — кто зависит от тебя

core-models/     ← высокий Ca
├── User
├── Order
└── TrainingJob

many services depend on core-models

→ Должен быть стабильным!

🔸 Efferent (Ce) — от кого ты зависишь

notification-service/  ← высокий Ce
├── depends on: email-service
├── sms-gateway
└── slack-api

→ Может меняться, но сам — менее переиспользуем

💡 Идеальный компонент:

Высокий Ca + низкий Ce = ядро системы

Низкий Ca + высокий Ce = периферия

4. Никаких циклов!

Циклические зависимости — тупик

❌ Цикл = смерть

auth-service → order-service → auth-service
                     ↑                ↓
                 payment-service ←────┘

🔹 Нельзя развернуть отдельно
🔹 Нельзя протестировать изолированно

✅ Решение: события или shared kernel

# Через событие
event_bus.publish("user_registered", user_id)

# или через общий сервис
identity_service.get_user(user_id)

→ Разрыв цикла

5. Границы = контракты

На границе — не код, а договор

🔸 Пример: вход в систему

POST /login
{
"email": "user@example.com",
"password": "secret"
}

← 200 OK
{
"token": "xyz",
"expires": "2025-12-31"
}

← 401 Unauthorized
{
"error": "invalid_credentials"
}

🔹 Это контракт. Его нарушили — сломали систему.

🔸 ORM — тоже граница

class UserModel(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    email = Column(String)

# ↔ между domain и database
# При изменении таблицы — меняется только этот класс

ORM — не просто удобство. Это **защита домена**

🎯 Зачем это всё?

Чтобы понять, зачем дальше:

  • Микросервисы — без принципов = хаос
  • ORM — граница между кодом и БД
  • CQRS — разделение чтения и записи
  • Event Sourcing — когда нужно audit