Ответ
Domain-Driven Design (DDD) — это подход к разработке сложного программного обеспечения, который фокусируется на глубоком моделировании предметной области (домена) бизнеса. Его цель — создать гибкую, понятную и легко поддерживаемую систему, которая непосредственно отражает бизнес-процессы и правила.
Ключевые концепции и строительные блоки:
-
Ubiquitous Language (Единый язык):
- Создание общего словаря терминов между разработчиками, экспертами предметной области (domain experts) и всеми участниками проекта.
- Этот язык используется в коде, документации и обсуждениях, устраняя недопонимание.
- Пример: В банковской сфере термины "Счет", "Клиринг", "Овердрафт" имеют четкие, согласованные определения.
-
Bounded Context (Ограниченный контекст):
- Явное определение границ, внутри которых конкретная модель и единый язык являются согласованными и применимыми.
- Один и тот же термин в разных контекстах может иметь разное значение.
- Пример: Сущность
Productв контексте "Каталог" имеет атрибутыName,Description,Price. В контексте "Логистика" та же сущностьProductимеет атрибутыWeight,Dimensions,StorageRequirements.
-
Aggregate (Агрегат) и Aggregate Root (Корень агрегата):
- Агрегат — это кластер из связанных объектов (сущностей и value objects), который трактуется как единое целое для операций изменения данных.
-
Aggregate Root (AR) — единственная точка входа в агрегат. Внешние объекты могут ссылаться только на AR, а не на внутренние сущности. AR отвечает за инварианты (бизнес-правила) всего агрегата.
// Aggregate Root public class Order : AggregateRoot { public Guid Id { get; private set; } public OrderStatus Status { get; private set; } private readonly List<OrderLine> _lines = new(); // Внутренняя сущность public IReadOnlyCollection<OrderLine> Lines => _lines.AsReadOnly(); // Все изменения идут через методы AR public void AddLine(Product product, int quantity) { // Проверка инварианта: нельзя добавлять товары в завершенный заказ if (Status == OrderStatus.Completed) throw new DomainException("Cannot modify a completed order."); var line = new OrderLine(product.Id, product.Price, quantity); _lines.Add(line); // Генерация Domain Event AddDomainEvent(new OrderLineAddedEvent(Id, product.Id, quantity)); } public void MarkAsCompleted() { ... } }
// Value Object (неизменяемый) public record OrderLine : ValueObject { public Guid ProductId { get; } public Money Price { get; } // Другой Value Object public int Quantity { get; } public Money Total => Price * Quantity; // Конструктор и equality на основе всех полей }
-
Repository (Репозиторий):
- Абстракция, которая инкапсулирует логику доступа к агрегатам (обычно через персистентность).
- Работает только с Aggregate Roots.
IOrderRepository.GetByIdAsync(id)возвращаетOrder(AR) со всеми егоOrderLines.
-
Domain Event (Доменное событие):
- Факт, что в домене что-то значимое произошло (например,
OrderPlacedEvent,PaymentReceivedEvent). - Позволяет разным частям системы (внутри или даже вне bounded context) реагировать на изменения, не создавая жестких зависимостей.
- Факт, что в домене что-то значимое произошло (например,
-
Слоистая/Гексагональная архитектура:
- DDD часто реализуется с четким разделением на Domain Layer (чистая бизнес-логика, без зависимостей от инфраструктуры), Application Layer (координация задач, транзакций), Infrastructure Layer (реализация репозиториев, вызов внешних сервисов).
Когда использовать DDD?
- Проекты со сложной и нетривиальной бизнес-логикой.
- Когда требуется долгосрочная поддержка и активная эволюция продукта.
- Когда критично точное соответствие программной модели реальным бизнес-процессам.
Когда НЕ использовать DDD?
- Простые CRUD-приложения без сложных бизнес-правил.
- Прототипы или проекты с очень коротким сроком жизни.
- Ситуации, где накладные расходы на сложность архитектуры не окупаются.