Что такое Domain-Driven Design (DDD) и каковы его ключевые концепции?

Ответ

Domain-Driven Design (DDD) — это методология разработки сложного программного обеспечения, в центре которой находится предметная область (домен) и бизнес-логика. Главная цель — смоделировать бизнес-процессы в коде, используя общий язык (Ubiquitous Language) между разработчиками и экспертами домена.

Ключевые концепции:

  • Домен (Domain): Предметная область, для которой создается приложение. Например, «логистика», «банкинг».
  • Сущность (Entity): Объект, обладающий уникальным идентификатором и жизненным циклом. Например, Пользователь с UserID.
  • Объект-значение (Value Object): Неизменяемый (immutable) объект без уникального идентификатора, характеризуемый своими атрибутами. Например, Адрес или ДенежнаяСумма.
  • Агрегат (Aggregate): Кластер из связанных сущностей и объектов-значений, который рассматривается как единое целое. У агрегата есть корень (Aggregate Root) — единственная сущность, через которую происходит взаимодействие с другими частями агрегата. Это обеспечивает целостность данных и соблюдение бизнес-правил.
  • Репозиторий (Repository): Абстракция, которая предоставляет интерфейс для доступа к агрегатам, имитируя коллекцию объектов в памяти. Скрывает детали хранения данных (БД, файлы и т.д.).

Пример на Go:

// Order является корнем агрегата (Aggregate Root).
// Весь агрегат включает Order и OrderItem. ype Order struct {
    ID          string
    CustomerID  string
    Items       []*OrderItem // Коллекция объектов-значений
    totalPrice  float64
}

// AddItem - метод, инкапсулирующий бизнес-логику.
// Изменения внутри агрегата проходят через его корень.
func (o *Order) AddItem(productID string, quantity int, price float64) {
    item := &OrderItem{
        ProductID: productID,
        Quantity:  quantity,
        Price:     price,
    }
    o.Items = append(o.Items, item)
    o.totalPrice += price * float64(quantity)
}

// OrderItem - объект-значение (Value Object).
// У него нет своего ID, он определяется в контексте заказа.
type OrderItem struct {
    ProductID string
    Quantity  int
    Price     float64
}

// OrderRepository определяет контракт для хранения и извлечения агрегатов Order.
type OrderRepository interface {
    Save(order *Order) error
    FindByID(id string) (*Order, error)
}

DDD наиболее эффективен в больших и сложных проектах, где бизнес-логика является основной сложностью.

Ответ 18+ 🔞

А, ДДД, ёпта! Domain-Driven Design, блядь! Это ж не просто очередная модная хуйня с конференций, это, сука, целая философия, как не сойти с ума, когда бизнес-логика сложнее, чем устройство марсианского ровера!

Смотри, в чём прикол. Берёшь ты какую-нибудь ебучую предметную область — ну, там, «логистику» или «банкинг». И вместо того, чтобы сразу лепить таблички в базе и писать на них SQL-запросы, ты начинаешь с ней разговаривать. Да-да, внатуре! Садишься с этими экспертами, которые в теме, и выковыриваешь у них, как оно на самом деле работает. И рождается у вас общий язык (Ubiquitous Language). Не «поле customer_id типа bigint», а «Идентификатор клиента», блядь! И этим языком потом и код пишешь, и диаграммы рисуешь, и даже на стенке в туалете можешь им писать — чтобы все, сука, дышали одним воздухом!

А теперь ключевые фишки, без которых нихуя не понять:

  • Домен (Domain) — это вот та самая вселенная, в которой мы ковыряемся. «Доставка пиццы», «онлайн-кинотеатр» — вот это всё.
  • Сущность (Entity) — это персонаж в этой вселенной, у которого есть имя и история. Как актёр в паспорте: у него есть ID, и он меняется со временем. ПользовательВася с ID=123 — это всегда ПользовательВася, даже если он сменил аватарку на котика.
  • Объект-значение (Value Object) — а это уже статист. У него нет паспорта, его определяет костюм. Адрес {Улица Ленина, 5}. Два адреса с одинаковыми полями — это один и тот же адрес, хоть ты тресни. Они неизменяемые, как будто в камне высечены.
  • Агрегат (Aggregate) — вот тут начинается магия, блядь! Это такая банда, кластер, семья. Куча связанных сущностей и объектов-значений, которые живут и умирают как одно целое. И в этой банде есть главарькорень агрегата (Aggregate Root). Хочешь поговорить с кем-то из банды? Иди через главаря! Это чтобы целостность не нарушить, правила бизнеса не проебать. Представь, пытаешься изменить позицию в заказе, минуя сам заказ — пиздец наступает, логика рушится.
  • Репозиторий (Repository) — это такой волшебный шкаф. Ты говоришь ему: «Дай мне заказ номер 42» или «Сохрани этот заказ». А он уже сам знает, лезть ли ему в базу данных, в файл или в облако ёбаное. Абстракция, блядь, чистейшая вода!

Смотри, как это в коде на Go выглядит, я тебе нарисую:

// Order — это наш ГЛАВАРЬ, корень агрегата. Весь его отряд — это Order и его OrderItem'ы.
type Order struct {
    ID          string
    CustomerID  string
    Items       []*OrderItem // Это его подчинённые, объекты-значения
    totalPrice  float64
}

// AddItem — вся бизнес-логика тут, внутри главаря! Не снаружи!
// Хочешь добавить позицию? Иди через него, сука!
func (o *Order) AddItem(productID string, quantity int, price float64) {
    item := &OrderItem{
        ProductID: productID,
        Quantity:  quantity,
        Price:     price,
    }
    o.Items = append(o.Items, item)
    o.totalPrice += price * float64(quantity) // Правило соблюдено, всё чики-пуки!
}

// OrderItem — типичный объект-значение. Без своего ID, как сирота.
// Определяется только тем, что внутри. ProductID="молоко", Quantity=2 — вот и всё описание.
type OrderItem struct {
    ProductID string
    Quantity  int
    Price     float64
}

// OrderRepository — контракт для нашего волшебного шкафа.
// «Сохрани заказ», «Найди заказ по ID» — больше от него нихуя не надо.
type OrderRepository interface {
    Save(order *Order) error
    FindByID(id string) (*Order, error)
}

И главное, запомни: DDD — это не серебряная пуля, ёпта! Тащить эту архитектуру на проект-одностраничник про погоду — это всё равно что ехать на КамАЗе за хлебом в соседний магазин. Овердохуища сложности, а толку — ноль. Оно имеет смысл только когда сама бизнес-логика — это такой спутанный клубок, что без общего языка и чётких границ ты просто сойдёшь с ума, пытаясь понять, где чей customer_id и почему его нельзя просто так обновить.

Вот такая, блядь, философия. Сначала кажется, что муторно, но когда влетаешь в проект, где без этого уже давно пиздец и хаос, понимаешь — Тургенев, блядь, отдыхает! Тут трагедии покруче, чем «Муму» топить!