← Назад к вопросам

Как применяешь DDD?

2.7 Senior🔥 131 комментариев
#Soft Skills и карьера#Микросервисы и архитектура

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Применение Domain-Driven Design в Go-разработке

Как Senior Go Developer, я применяю Domain-Driven Design (DDD) для создания сложных бизнес-систем с богатой предметной областью. В Go, который является прагматичным и эффективным языком, DDD помогает структурировать код вокруг бизнес-логики, делая его более понятным, поддерживаемым и устойчивым к изменениям.

Стратегический дизайн и коммуникация

Первым шагом всегда является глубокое погружение в предметную область через сотрудничество с экспертами (domain experts). Мы проводим сессии по выявлению ограниченных контекстов (Bounded Contexts) и создаем контекстные карты (Context Maps). В Go это часто отражается в структуре модулей/пакетов и явных границах между ними.

// Пример декомпозиции на ограниченные контексты в структуре проекта
project/
├── order_context/           // Контекст управления заказами
├── payment_context/        // Контекст обработки платежей
├── inventory_context/      // Контекст управления запасами
└── shared_kernel/          // Общие модели (например, Money, Email)

Тактический дизайн в Go

В рамках каждого ограниченного контекста я применяю тактические паттерны DDD, адаптированные под особенности Go.

1. Агрегаты (Aggregates)

Агрегаты являются ключевым элементом для инкапсуляции бизнес-правил. В Go я реализую их как структуры с методами, которые гарантируют инварианты.

// OrderAggregate - агрегат заказа с root entity Order
type Order struct {
    ID         uuid.UUID
    Items      []OrderItem
    Status     OrderStatus
    TotalPrice Money
}

func (o *Order) AddItem(item OrderItem) error {
    // Проверка бизнес-правил перед изменением
    if o.Status != OrderStatusDraft {
        return ErrOrderNotEditable
    }
    // Инкапсуляция логики изменения
    o.Items = append(o.Items, item)
    o.recalculateTotal()
    return nil
}

func (o *Order) recalculateTotal() {
    // Приватный метод для внутренней логики
    total := NewMoney(0, "USD")
    for _, item := range o.Items {
        total = total.Add(item.Price)
    }
    o.TotalPrice = total
}

2. Репозитории (Repositories)

Репозитории предоставляют абстракцию для персистентности. В Go я использую интерфейсы для отделения доменной логики от деталей реализации хранилища.

type OrderRepository interface {
    FindByID(id uuid.UUID) (*Order, error)
    Save(order *Order) error
    FindByCustomerID(customerID uuid.UUID) ([]*Order, error)
}

// PostgreSQL реализация
type PostgresOrderRepository struct {
    db *sql.DB
}

func (r *PostgresOrderRepository) Save(order *Order) error {
    // Преобразование агрегата в модель хранилища
    // и выполнение SQL операций
}

3. Сервисы домена (Domain Services)

Для операций, которые не естественно принадлежат сущности или агрегату, я создаю доменные сервисы.

type OrderProcessingService struct {
    orderRepo   OrderRepository
    paymentRepo PaymentRepository
}

func (s *OrderProcessingService) ProcessOrder(orderID uuid.UUID) error {
    order, err := s.orderRepo.FindByID(orderID)
    if err != nil {
        return err
    }
    
    // Комплексная бизнес-операция, затрагивающая несколько агрегатов
    payment := CreatePaymentFromOrder(order)
    err = s.paymentRepo.Save(payment)
    if err != nil {
        return err
    }
    
    order.Status = OrderStatusPaid
    return s.orderRepo.Save(order)
}

4. События домена (Domain Events)

Для реализации реактивных паттернов и интеграции между контекстами я использую события домена. В Go удобно использовать простые структуры.

type OrderPlacedEvent struct {
    OrderID     uuid.UUID
    CustomerID  uuid.UUID
    TotalAmount Money
    Timestamp   time.Time
}

func (o *Order) Place() (*OrderPlacedEvent, error) {
    // Проверка инвариантов
    if len(o.Items) == 0 {
        return nil, ErrEmptyOrder
    }
    
    o.Status = OrderStatusPlaced
    event := &OrderPlacedEvent{
        OrderID:    o.ID,
        CustomerID: o.CustomerID,
        TotalAmount: o.TotalPrice,
        Timestamp:  time.Now(),
    }
    
    return event, nil
}

Практические аспекты в Go-проектах

  • Моделирование через интерфейсы: Go интерфейсы идеально подходят для абстракций DDD, таких как репозитории и сервисы.
  • Идиоматика Go: Я слежу за тем, чтобы DDD паттерны не противоречили идиомам Go (например, избегаю чрезмерно сложных иерархий).
  • Инфраструктура и домен: Четкое разделение через папки/пакеты (domain, infrastructure, application).
  • Тестирование: DDD естественно приводит к тестируемому дизайну. Я пишу unit тесты для агрегатов и доменных сервисов, используя моки для репозиториев.
  • Минимаism: Go культивирует минимализм, поэтому я применяю только те паттерны DDD, которые действительно необходимы для конкретного контекста, избегая переусложнения.

DDD в Go — это не о строгом соблюдении всех паттернов, а о использовании принципов для создания чистой, выразительной доменной модели, которая является ядром системы и может эволюционировать вместе с бизнесом. Ключевой результат — снижение coupling между компонентами и повышение cohesion внутри доменного слоя.

Как применяешь DDD? | PrepBro