Назовите и опишите три основных принципа DDD (Domain-Driven Design).

Ответ

DDD (Domain-Driven Design) — это подход к разработке ПО, который фокусируется на предметной области. Ключевые принципы:

  1. Фокус на основной доменной логике. Главный приоритет — это бизнес-логика и бизнес-процессы. Архитектура приложения строится вокруг доменной модели, а не технических аспектов (базы данных, фреймворки). Доменные объекты должны точно отражать сущности и правила реального мира.

  2. Разделение на Ограниченные Контексты (Bounded Contexts). Сложная доменная модель разбивается на несколько независимых, более мелких моделей — ограниченных контекстов. У каждого контекста есть свои четкие границы, своя модель и своя терминология. Это позволяет избежать создания единой, громоздкой и противоречивой модели для всей системы.

  3. Создание Единого Языка (Ubiquitous Language). Это общий язык, который используется всеми участниками проекта: разработчиками, бизнес-аналитиками, экспертами предметной области. Термины из этого языка используются как в обсуждениях, так и непосредственно в коде (названия классов, методов, переменных). Это устраняет недопонимание и делает код самодокументируемым.

Пример на Go:

// Order — это Агрегат (Aggregate Root), ключевая сущность в нашем домене.
// Он гарантирует целостность всех своих внутренних частей (Items).
type Order struct {
    ID       uuid.UUID
    Items    []Item
    Status   OrderStatus
}

// MarkAsPaid — метод, инкапсулирующий бизнес-правило.
// Логика изменения статуса находится внутри самой сущности, а не во внешних сервисах.
func (o *Order) MarkAsPaid() error {
    if o.Status != OrderStatusPending {
        return errors.New("заказ может быть оплачен только из статуса 'в ожидании'")
    }
    o.Status = OrderStatusPaid
    // Здесь могут быть и другие доменные события, например, OrderPaidEvent
    return nil
}

Ответ 18+ 🔞

А, ДДД, ёпта! Ну это ж классика, блядь! Подход, который все хвалят, а на деле применяют так, что потом сам Толстой с Тургеневым в гробу переворачиваются, в рот меня чих-пых!

Смотри, суть-то проще, чем кажется, если не заумничать.

Представь, ты делаешь не просто программу, а целый мир, блядь. Мир заказов, товаров, клиентов — вот это всё и есть твоя предметная область. И первое, главное правило — всё пляшет от бизнес-логики. Не от того, какую базу данных ты выберешь, или какой фреймворк модный. Нет! Ты сначала должен понять, как там, в этом бизнесе, всё на самом деле работает, какие правила, а потом уже код под это подгонять. Твои объекты в коде — это прям отражение реальных сущностей, а не просто структуры для БД. Иначе получится пиздопроебибна, а не система.

Дальше, самое важное, что всех спасает от ебалы мозга — Ограниченные Контексты (Bounded Contexts). Слышишь? ОГРАНИЧЕННЫЕ, блядь! Не пытайся запихнуть всю вселенную в один модуль! Это как если бы ты в одной комнате пытался и готовить, и спать, и срать. Получится бардак, вонь и пиздец. Вот и в коде так же. Разбивай свою огромную, страшную систему на куски. В одном куске (контексте) «Заказ» — это одно, с одними правилами. В другом куске «Отчётности» этот же «Заказ» — это просто строка с цифрами, ему похуй на все твои доменные правила. У каждого контекста свой язык, свои модели, свои границы. И они друг с другом общаются чётко, через определённые каналы, а не лезут друг другу в кишки. Иначе — волнение ебать, терпения ноль ебать, все друг друга заебали.

И третий столп, без которого всё летит в пизду — Единый Язык (Ubiquitous Language). Это когда ты, бизнес-аналитик, менеджер и тот самый чувак, который реально работает в этой сфере, говорите на одном языке. И этот же язык — прям в коде! Не «SomeEntityDTOFactory», а «Order», «MarkAsPaid», «Cancel». Если бизнес говорит «заблокировать счёт», то в коде метод так и называется BlockAccount(), а не UpdateUserStatus(3). Иначе получается, как в том анекдоте: «Муму!» — а нихуя не понятно, кто серит на крыше.

Вот, смотри на пример, тут всё по-честному:

// Order — это Агрегат, корень всей хуйни в этом контексте.
// Он сам за себя отвечает, сам свои правила соблюдает.
type Order struct {
    ID       uuid.UUID
    Items    []Item
    Status   OrderStatus
}

// MarkAsPaid — смотри, бизнес-правило прямо здесь, внутри сущности!
// Не где-то в сервисе на 1000 строк. Сущность сама знает, когда её можно оплатить.
func (o *Order) MarkAsPaid() error {
    if o.Status != OrderStatusPending {
        return errors.New("заказ может быть оплачен только из статуса 'в ожидании'")
    }
    o.Status = OrderStatusPaid
    // Тут ещё можно событие какое-нибудь выстрелить, типа OrderPaidEvent, чтобы другие части системы узнали.
    return nil
}

Видишь? Всё наглядно. Логика там, где ей и положено быть. Не размазана по десяти файлам. Это и есть ДДД в здоровом, не вырожденном виде. А то некоторые начитаются умных книжек и начинают на ровном месте городить такие абстракции, что потом сами не могут понять, что написали. Хуй с горы, короче. Главное — смысл уловить, а не терминами кидаться.