Ответ
Domain-Driven Design (DDD) — это подход к разработке сложного программного обеспечения, который фокусируется на глубоком моделировании предметной области (бизнес-логики) и её отражении в коде. Основная цель — создание гибкой, понятной и легко изменяемой системы, которая эволюционирует вместе с бизнесом.
Ключевые строительные блоки DDD:
- Доменная модель (Domain Model): Сердце системы. Это не просто "база данных", а набор взаимосвязанных объектов (сущностей, value-объектов), которые инкапсулируют бизнес-правила и поведение.
- Универсальный язык (Ubiquitous Language): Общий язык для общения между разработчиками и экспертами предметной области. Термины из этого языка напрямую становятся названиями классов, методов и модулей в коде.
- Ограниченный контекст (Bounded Context): Чёткая граница, внутри которой определённая модель и универсальный язык являются согласованными. Большая система разбивается на такие контексты (например, "Заказы", "Доставка", "Каталог").
- Сущность (Entity): Объект, имеющий уникальный идентификатор, который остаётся неизменным на протяжении всего жизненного цикла (например,
UserсuserId). - Объект-значение (Value Object): Объект, не имеющий идентификатора и определяемый исключительно своими атрибутами (например,
Moneyсamountиcurrency). Они неизменяемы (immutable). - Агрегат (Aggregate): Кластер связанных сущностей и value-объектов, рассматриваемых как единое целое для операций изменения. У агрегата есть корень (Aggregate Root) — единственная сущность, через которую происходит всё внешнее взаимодействие.
- Репозиторий (Repository): Абстракция, предоставляющая доступ к агрегатам, скрывая детали хранения данных (например,
IOrderRepository). - Доменный сервис (Domain Service): Операция или логика, которая по своей природе не принадлежит ни одной сущности или value-объекту (например, сложный расчёт стоимости доставки, затрагивающий несколько агрегатов).
Практический пример на C# (контекст "Заказы"):
// Value Object
public record Address(string Street, string City, string PostalCode);
// Entity
public class OrderItem
{
public ProductId ProductId { get; private set; }
public Money Price { get; private set; }
public int Quantity { get; private set; }
// ... логика создания и изменения
}
// Aggregate Root
public class Order : AggregateRoot<OrderId>
{
private readonly List<OrderItem> _items = new();
public CustomerId CustomerId { get; private set; }
public Address ShippingAddress { get; private set; }
public IReadOnlyList<OrderItem> Items => _items.AsReadOnly();
public void AddItem(Product product, int quantity)
{
// Бизнес-правило: нельзя добавить товар с нулевым количеством
if (quantity <= 0) throw new DomainException("Quantity must be positive.");
var existingItem = _items.FirstOrDefault(i => i.ProductId == product.Id);
if (existingItem != null)
{
// Если товар уже есть, увеличиваем количество
existingItem.IncreaseQuantity(quantity);
}
else
{
// Иначе создаём новый элемент заказа
_items.Add(new OrderItem(product.Id, product.Price, quantity));
}
// Генерируем доменное событие OrderItemAdded
AddDomainEvent(new OrderItemAdded(this.Id, product.Id, quantity));
}
}
// Repository Interface (определяется в доменном слое)
public interface IOrderRepository : IRepository<Order>
{
Task<Order?> GetByIdAsync(OrderId id, CancellationToken cancellationToken = default);
Task AddAsync(Order order, CancellationToken cancellationToken = default);
}
DDD не является серебряной пулей и требует значительных усилий. Он наиболее оправдан в проектах со сложной, постоянно меняющейся бизнес-логикой, где правильность и ясность кода напрямую влияют на успех бизнеса.
Ответ 18+ 🔞
Слушай, а вот есть такая штука — Domain-Driven Design, или DDD, если по-пацански. Это, блядь, не просто очередной модный фреймворк, который за неделю выучил и щёлкаешь как орехи. Это, ёпта, целая философия, как не сойти с ума, когда пишешь какую-нибудь пиздопроебибну систему для банка или логистики, где правил больше, чем у деда в гараже.
Основная идея — перестать, наконец, переводить требования бизнеса в какие-то свои кривые таблички в базе, а начать говорить на одном языке с этими самыми бизнес-экспертами. Представляешь? Ты приходишь к ним, а они тебе: «Нам нужно, чтобы заказ резервировал инвентарь, но только после валидации кредитного лимита, и если лимит превышен, то запускается процесс ручного одобрения». И ты вместо того, чтобы кивать и потом неделю ебаться с непонятными полями в БД, говоришь: «Окей, значит у нас есть Агрегат Заказ, который вызывает Доменный Сервис ПроверкаКредита, и если всё плохо — кидает Событие ЗаказТребуетОдобрения». И они такие: «Да, бля, именно так!». Вот это и есть Универсальный язык. И эти же термины потом и в коде будут. Красота же!
А теперь по косточкам, что там за кирпичики в этой стройке:
-
Доменная модель — это, сука, святое. Это не ваша база данных, нет. Это живое воплощение всех этих бизнес-правил, страхов и хотелок в виде кода. Если в бизнесе говорят «деньги нельзя просто так взять и поделить, там курс валюты», то в коде у тебя будет объект
Moneyс полямиAmountиCurrency, а не простоdecimal totalPrice. Понимаешь разницу? Доверия ебать ноль к голым числам. -
Ограниченный контекст — вот это вообще гениальная мысль. Это признание того, что «пользователь» в контексте «Доставки» и «пользователь» в контексте «Оплаты» — это две разные, блядь, сущности. В одном от него нужен только адрес, а в другом — номер карты. И не надо лепить из этого одного монстра на все случаи жизни, который потом хуй с горы не сдвинешь. Разбиваешь систему на такие изолированные модули-контексты, и в каждом живёт своя модель со своим языком.
-
Сущность и Объект-значение — основа основ. Сущность — это когда важна идентичность.
Userс ID — он один, уникальный, его можно найти и отследить. АAddress(улица, город, индекс) — это объект-значение. Два адреса с одинаковыми полями — это один и тот же адрес, да похуй на их ID. Value Object'ы должны быть неизменяемыми, как скала. Создал — и забыл. Это чтобы потом в 3 часа ночи не искать, кто и где перезаписал тебе город в объекте. -
Агрегат — это, можно сказать, банда. Группа связанных сущностей, которые всегда действуют заодно. Например,
Заказ(корень агрегата) и все егоПозицииЗаказа. Нельзя просто так взять и изменить позицию в обход заказа. Все изменения — только через главного, через корень (Order). Это чтобы сохранять целостность, а не получить потом хитрую жопу, где данные друг другу противоречат. -
Репозиторий — это такая умная обёртка над твоим способом хранения (база, файл, облако). Ты в доменной логике просто говоришь:
orderRepository.AddAsync(myOrder). А уж это сохранится в SQL, в MongoDB или в оперативку — твоему домену абсолютно похуй. Главное — контракт. -
Доменный сервис — это для операций, которые как бы всем мешают. Ну вот, например, логика «рассчитать стоимость доставки». Она нужна и заказу, и корзине, и окошку с предпросчётом. Выносишь её в отдельный сервис
DeliveryCostCalculatorи пользуешься, где надо. Чисто, без привязки к какой-то одной сущности.
Ну и смотри, как это может выглядеть в коде, на примере того же заказа:
// Value Object — просто набор данных, неизменяемый. Сравниваются по полям.
public record Address(string Street, string City, string PostalCode);
// Aggregate Root — главный в агрегате. Все изменения через него.
public class Order : AggregateRoot<OrderId>
{
private readonly List<OrderItem> _items = new();
public Address ShippingAddress { get; private set; }
// Основная бизнес-логика тут
public void AddItem(Product product, int quantity)
{
// Правило: ноль или отрицательное количество — никуда не годится.
if (quantity <= 0) throw new DomainException("Ты чё, мудила? Количество должно быть больше нуля!");
var existingItem = _items.FirstOrDefault(i => i.ProductId == product.Id);
if (existingItem != null)
{
// Если уже есть в заказе — увеличиваем количество.
existingItem.IncreaseQuantity(quantity);
}
else
{
// Иначе — создаём новую позицию.
_items.Add(new OrderItem(product.Id, product.Price, quantity));
}
// Генерируем событие, что что-то поменялось. Это для других частей системы.
AddDomainEvent(new OrderItemAdded(this.Id, product.Id, quantity));
}
}
Короче, DDD — это мощно, но это овердохуища работы. Это не для стартапа из трёх человек, где надо за неделю наколбасить прототип. Это оружие для сложных, долгоживущих проектов, где бизнес-логика — это твой главный враг и лучший друг одновременно. Если применять с умом — система получается гибкой, как гимнастка, и понятной, как два пальца. Если халтурить — получится просто манда с ушами, перегруженная абстракциями. Выбор за тобой.