Ответ
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-приложения без сложных бизнес-правил.
- Прототипы или проекты с очень коротким сроком жизни.
- Ситуации, где накладные расходы на сложность архитектуры не окупаются.
Ответ 18+ 🔞
Ну слушай, вот есть у тебя проект, а в нём какая-нибудь ебанутая бизнес-логика, как у банка или страховой. Там не просто кнопочку нажать — там правила на правилах, исключения на исключениях. Так вот, Domain-Driven Design — это такой подход, чтобы не сойти с ума, пытаясь эту всю хуйню в код запихнуть. Суть в том, чтобы твоя программа говорила на том же языке, что и твои заказчики-эксперты, эти, блядь, бизнес-аналитики.
Основные фишки, без которых нихуя не понятно:
-
Единый язык (Ubiquitous Language):
- Это когда вы с экспертами и между собой договариваетесь, что «клиент» — это вот это, «заявка» — это вот так, а «просрочка» — это вот эта хуйня. И этот же самый язык ты потом пишешь в коде. Чтобы не было такого, что ты говоришь «юзер», а он имеет в виду «аккаунт», и вы оба думаете, что поняли друг друга, а на деле — пиздец и два дня дебага.
-
Ограниченный контекст (Bounded Context):
- Это, блядь, самое важное. Один и тот же термин в разных отделах — это разные сущности. Вот смотри: «Товар» для отдела продаж — это картинка, название и цена, чтобы быстрее впарить. А «Товар» для склада — это габариты, вес и температура хранения, чтобы он не сгнил. Это два РАЗНЫХ контекста! И пытаться слепить из них одну универсальную хуйню на все случаи жизни — это прямой путь в ад. Делай две разные модели и не мучайся.
-
Агрегат и Корень агрегата (Aggregate & Aggregate Root):
-
Представь, что у тебя есть «Заказ». В нём есть сам заказ, список позиций, платёжка, доставка. Так вот, Агрегат — это весь этот кластер связанной хуеты, которую ты изменяешь как единое целое. А Корень — это главная сущность, через которую идёт всё общение с внешним миром. Не лезь ты внутрь агрегата и не пытайся поменять позицию заказа напрямую! Только через методы корня. Он там свои инварианты (бизнес-правила, блядь) проверит.
// Вот он, Корень агрегата. Царь и бог. public class Order : AggregateRoot { public Guid Id { get; private set; } private List<OrderLine> _lines = new(); // Список позиций — внутри, приватный. // Хочешь добавить позицию? Делай только так! public void AddItem(Product product, int quantity) { // А вот и бизнес-правило: в отменённый заказ хуй что добавишь. if (Status == OrderStatus.Cancelled) throw new DomainException("Иди нахуй, заказ отменён!"); _lines.Add(new OrderLine(product.Id, quantity)); } // Больше никто не может _lines тронуть. Красота. }
-
-
Репозиторий (Repository):
- Это такая прослойка, которая делает вид, что твои агрегаты живут в какой-то волшебной коллекции в памяти. Хочешь заказ?
orderRepository.Get(id). Сохранить?orderRepository.Save(order). А то, что там внутри база данных, кэш или файл на диске — это твои личные проблемы. Репозиторий работает только с Корнями агрегатов, запомни это.
- Это такая прослойка, которая делает вид, что твои агрегаты живут в какой-то волшебной коллекции в памяти. Хочешь заказ?
-
Доменное событие (Domain Event):
- Это когда в домене происходит что-то важное, о чём должны узнать другие. Заказ создан? Вызывай событие
OrderCreatedEvent. И пусть другие модули (например, служба нотификаций или склад) на него подписываются и делают что надо. Главное — не превращать это в спагетти-код, где всё зависит от всего.
- Это когда в домене происходит что-то важное, о чём должны узнать другие. Заказ создан? Вызывай событие
-
Архитектура:
- Всё это безобразие обычно раскладывают по слоям. Доменный слой — это святое. Только бизнес-логика, ни одной ссылки на базу данных, HTTP или прочую инфраструктуру. Слой приложения — тут уже координация, вызов репозиториев. Инфраструктурный слой — тут уже все эти грязные подробности работы с внешним миром.
Когда это всё городить? Да когда проект реально сложный, с кучей правил, которые ещё и меняются каждые полгода. Когда от твоего кода зависит, будет компания в плюсе или в ауте.
Когда НЕ надо? Ну, если ты делаешь простенький бложик или прототип на неделю. Не обвешивай простую CRUD-форму по учёту котиков этим всем пафосом. Получится как с танком на дачу — мощно, но дохуя сложно и бензина жрёт как не в себя.