1. Основы DDD и инверсия зависимостей в Python
Философия предметно-ориентированного проектирования
Domain-Driven Design (DDD) — это архитектурный подход к разработке программного обеспечения, который ставит во главу угла бизнес-логику и реальные процессы компании, а не технические детали реализации. Концепцию впервые формализовал Eric Evans в 2003 году. Главная идея заключается в том, что сложность программного продукта должна исходить из сложности самой предметной области, а не из запутанности кода или выбранного фреймворка.
Единый язык (Ubiquitous Language) — это фундаментальная практика DDD. Разработчики, аналитики и эксперты предметной области (доменные эксперты) должны использовать единый словарь терминов. Если бизнес говорит «отгрузка товара», в коде должен быть класс или метод Shipment, а не Delivery или OrderSend.
> Разработка программного обеспечения — это процесс обучения, а рабочий код — побочный продукт этого процесса. > > Альберто Брандолини, эксперт по DDD
Ключевые составляющие предметно-ориентированного проектирования делятся на стратегические и тактические. Стратегическое проектирование оперирует крупными концепциями:
Предметная область (Domain*): Сфера деятельности компании, ее основные бизнес-процессы и правила. Поддомен (Subdomain*): Логическая часть предметной области. Например, в интернет-магазине это могут быть каталоги товаров, логистика и биллинг. Ограниченный контекст (Bounded Context*): Явная граница, внутри которой применима определенная модель предметной области и где Единый язык имеет строго определенное значение.
Проблема связанности кода и инфраструктуры
Исторически сложилось так, что многие проекты на Python, особенно использующие популярные веб-фреймворки, развиваются от базы данных. Разработчик создает таблицы, описывает ORM-модели, а затем навешивает на них бизнес-логику. Этот подход называется Data-Driven Design.
В небольших проектах это работает отлично, но по мере роста кодовой базы возникает сильная связность (Coupling). Бизнес-правила размазываются по контроллерам, сериализаторам и сигналам ORM. Изменение логики расчета скидки требует модификации SQL-запросов, а переход на другую базу данных или внешний API становится крайне сложной задачей.
| Характеристика | Data-Driven подход | Domain-Driven Design | | :--- | :--- | :--- | | Точка отсчета | Схема базы данных (таблицы, связи) | Бизнес-процессы и правила | | Где живет логика | В ORM-моделях, контроллерах, view-функциях | В чистых Python-классах (сущностях, агрегатах) | | Зависимости | Бизнес-логика зависит от фреймворка и БД | Фреймворк и БД зависят от бизнес-логики | | Тестируемость | Требует поднятия тестовой БД и моков | Легко тестируется unit-тестами без инфраструктуры |
Принцип инверсии зависимостей как фундамент архитектуры
Чтобы разорвать порочный круг зависимости от инфраструктуры, DDD опирается на Принцип инверсии зависимостей (Dependency Inversion Principle, DIP) — букву «D» в аббревиатуре SOLID.
Согласно классическому определению, модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба типа модулей должны зависеть от абстракций. При этом абстракции не должны зависеть от деталей, наоборот — детали должны зависеть от абстракций.
В контексте DDD модулем верхнего уровня является ядро системы — бизнес-логика (Домен). Модулями нижнего уровня выступают база данных, веб-сервер, отправка email и сторонние API. Домен не должен знать, используете ли вы PostgreSQL или MongoDB, Django или FastAPI.
Рассмотрим типичную ошибку проектирования, когда сервис напрямую зависит от конкретной реализации базы данных:
В этом примере OrderService невозможно протестировать без реального подключения к PostgreSQL. Если бизнес решит хранить заказы в другой системе, придется переписывать сам сервис.
Инверсия зависимостей решает эту проблему путем внедрения интерфейса (абстракции) между бизнес-логикой и инфраструктурой:
Теперь OrderService работает с любым объектом, который реализует интерфейс OrderRepository.
Пример из реальной практики: расчет стоимости корзины. Итоговая цена = Базовая цена - (Базовая цена × Скидка / 100). Если базовая цена составляет 15 000 руб., а персональная скидка клиента равна 15, то итоговая цена составит 12 750 руб. Эта математика является чистой бизнес-логикой. Она должна выполняться в доменной модели, которая получает данные о скидке через абстрактный репозиторий пользователей, не делая прямых SQL-запросов к таблицам.
Реализация абстракций в Python
В Python нет встроенного ключевого слова interface, как в строго типизированных языках (Java или C#). Для реализации абстракций разработчики используют два основных инструмента: модуль abc и typing.Protocol.
Модуль abc (Abstract Base Classes) позволяет создавать классы, от которых нельзя создать экземпляр напрямую. Наследники обязаны реализовать все методы, помеченные декоратором @abstractmethod. Это классический подход, обеспечивающий строгую проверку на этапе инициализации объекта.
С выходом PEP 544 в Python появилась поддержка структурной типизации через Protocol. Это позволяет описывать ожидаемое поведение объекта без необходимости явного наследования. Если класс имеет те же методы с теми же сигнатурами, что и протокол, статический анализатор (например, mypy) сочтет его совместимым.
Шаги для внедрения инверсии зависимостей в Python-проекте:
ABC или Protocol) для всех внешних зависимостей: баз данных, очередей сообщений, сторонних API.SqlAlchemyUserRepository, который реализует UserRepository).Переход на инверсию зависимостей и использование изолированных репозиториев кардинально меняет скорость разработки. В монолитном приложении с жесткой привязкой к БД прогон 500 тестов может занимать 45 секунд из-за постоянных операций записи и чтения с диска. При использовании изолированной доменной модели и InMemory репозиториев те же 500 тестов, проверяющих исключительно бизнес-правила в оперативной памяти, выполняются за 0.3 секунды. Разница в 150 раз позволяет разработчикам получать мгновенную обратную связь и безопасно проводить рефакторинг.