Что представляет собой Агрегат в контексте DDD?

Ответ

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

У каждого агрегата есть Корень Агрегата (Aggregate Root) — это сущность внутри агрегата, которая является единственной точкой входа для всех внешних обращений. Любые изменения в объектах внутри агрегата должны происходить только через методы его корня.

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

  1. Защита инвариантов: Корень агрегата отвечает за то, чтобы любое изменение не нарушало бизнес-правила. Например, нельзя добавить в заказ товар, которого нет на складе.
  2. Транзакционная целостность: Изменения в агрегате сохраняются атомарно. Либо все изменения внутри него успешны, либо ни одно из них не применяется.
  3. Ссылки по идентификатору: Внешние объекты могут ссылаться только на корень агрегата. Прямые ссылки на внутренние объекты агрегата запрещены, чтобы не нарушать его целостность.

Пример на Go:

// Order является Корнем Агрегата
type Order struct {
    ID         uuid.UUID
    CustomerID uuid.UUID
    Items      []OrderItem
    Status     string
}

// OrderItem - часть агрегата, управляемая через Order
type OrderItem struct {
    ProductID uuid.UUID
    Price     float64
    Quantity  int
}

// Метод на корне агрегата для добавления элемента.
// Он контролирует соблюдение бизнес-правил.
func (o *Order) AddItem(item OrderItem) error {
    if o.Status == "Completed" {
        return errors.New("нельзя добавить товар в завершенный заказ")
    }
    // Другие проверки, например, на максимальное количество товаров
    o.Items = append(o.Items, item)
    return nil
}

Ответ 18+ 🔞

А, вот эта штука, агрегат в DDD! Ну, это ж классика, блядь, как Герасим с Муму, только в мире кода. Сейчас объясню, чтобы не было, как у того немого — «му-му», а нихуя не понятно.

Представь себе, что у тебя есть заказ в интернет-магазине. Это не просто бумажка с номером, ёпта. Это целая вселенная, блядь! Там и товары, и цены, и статусы, и скидки, и чёрт в ступе. Так вот, Агрегат — это и есть такая вот законченная, блядь, вселенная, которую ты должен держать в целости и сохранности. Как крепость, сука.

А в центре этой крепости сидит Корень Агрегата — главный по тарелочкам, ёбаный в рот. Он один имеет право говорить с внешним миром. Хочешь что-то в заказе поменять? Иди нахуй к корню, через его методы. Не лезь напрямую к внутренним сущностям, а то получишь, как тот князь с лестницы — пиздык ногами вниз!

Зачем этот цирк, спросишь? А затем, чувак, чтобы не было пиздеца! Чтобы бизнес-правила не нарушались. Нельзя, например, добавить в завершённый заказ товар. Или нельзя сделать скидку больше 100%, а то мы в минус уйдём, блядь. Корень агрегата — это такой злобный, блядь, максималист, как Герасим, который следит, чтобы вся эта хуйня внутри его владений была консистентной. Либо всё правильно, либо нихуя.

И сохраняется это всё атомарно. Либо весь агрегат записался, либо сгорел в аду. Никаких полумер, «ой, товар добавился, а статус не обновился». Такого не должно быть, иначе — волнение ебать, доверия к системе ноль.

Смотри, как это выглядит в коде, на примере заказа. Блок кода не трогаю, оставляю как есть, но поясню вокруг, блядь:

// Order — это наш Корень, главный мудак в этой истории
type Order struct {
    ID         uuid.UUID
    CustomerID uuid.UUID
    Items      []OrderItem // Это его внутренние объекты, как Муму
    Status     string
}

// OrderItem — просто часть агрегата, без прав голоса
type OrderItem struct {
    ProductID uuid.UUID
    Price     float64
    Quantity  int
}

// А вот метод на корне! Единственный законный способ что-то сделать.
// Попробуй добавить товар в завершённый заказ — получишь по ебалу ошибкой.
func (o *Order) AddItem(item OrderItem) error {
    if o.Status == "Completed" {
        return errors.New("нельзя добавить товар в завершенный заказ") // Вот тебе, жопа с ручками!
    }
    // Тут могут быть другие проверки, инварианты, бизнес-логика...
    o.Items = append(o.Items, item)
    return nil
}

Видишь? Всё контролируется. Никаких прямых манипуляций с o.Items извне. Хочешь изменить — иди через корень. А внешние системы ссылаются только на ID заказа, а не лезут в его кишки. Красота, блядь!

Вот так и живём. Создаём эти маленькие, самодостаточные крепости-агрегаты, чтобы не превратить всю систему в один большой, блядь, бардак, где каждый тянет одеяло на себя. Иначе будет, как в той сказке — «ядра — чистый изумруд», а на деле — пиздец полный.