Доменно-ориентированное проектирование (DDD) в микросервисах

Доменно-ориентированное проектирование (DDD) в микросервисах

Когда организации переходят от монолитной архитектуры к микросервисам, они сталкиваются с критически важным вопросом: Как мы очерчиваем границы наших сервисов?

Теоретически микросервисы должны быть свободными, разделенными единицами, которые можно разрабатывать, развертывать и масштабировать независимо. Однако на практике многие команды в конечном итоге создают распределенный монолит — систему, в которой сервисы настолько тесно связаны, что одно изменение в бизнесе требует одновременного изменения и развертывания нескольких сервисов, что усугубляет задержки в сети и тупики при развертывании.

Чтобы избежать этой ловушки, архитекторы программного обеспечения обращаются к Domain-Driven Design (DDD). DDD, впервые представленный Эриком Эвансом в 2003 году, представляет собой методологию разработки программного обеспечения, которая согласовывает структуры кода со сложными бизнес-областями, которые они представляют.

В этой статье мы рассмотрим, как DDD предоставляет стратегические и тактические схемы для разработки чистых, изолированных и легко поддерживаемых микросервисов.


1. Стратегический план: определение границ услуг

DDD делится на два основных этапа: Стратегическое проектирование и Тактическое проектирование. Стратегический дизайн – это моделирование бизнес-контекста и определение границ услуг. Он обеспечивает «макро» представление об архитектуре.

Сопоставление ограниченных контекстов DDD со схемой архитектуры микросервисов

А. Вездесущий язык

В рамках бизнеса разные отделы используют одни и те же слова для обозначения совершенно разных вещей. Например:

  • Для команды продаж «Пользователь» — это ведущий или потенциальный клиент.
  • Для команды безопасности «Пользователь» — это набор учетных данных для входа.
  • Для команды доставки «Пользователь» — это физический адрес получателя.

Попытка создать единую, унифицированную модель базы данных для «Пользователя», удовлетворяющую всех, приводит к созданию огромной и запутанной базы кода. DDD решает эту проблему, создавая повсеместный язык — общий словарь, определенный и используемый как экспертами в области бизнеса, так и разработчиками в пределах определенных границ.

Б. Ограниченные контексты

Ограниченный контекст — это явная граница, внутри которой применяется модель предметной области. Внутри границы все термины вездесущего языка имеют одно и недвусмысленное значение.

Вместо одной модели «Пользователь» мы определяем отдельные модели в соответствующих ограниченных контекстах:

  • В Контексте заказа у нас есть модель Order, содержащая контактную информацию клиента.
  • В Контексте идентификации у нас есть модель Credentials.
  • В разделе Контекст доставки у нас есть модель DeliveryAddress.

Золотое правило микросервисов: Один ограниченный контекст напрямую соответствует одному микросервису.

Разделяя систему на ограниченные контексты, мы гарантируем, что микросервисы слабо связаны и представляют отдельные бизнес-возможности.

C. Сопоставление контекста: как взаимодействуют сервисы

Ограниченные контексты не существуют изолированно; они должны взаимодействовать. Карта контекстов определяет отношения и механизмы перевода между контекстами. Ключевые шаблоны включают в себя:

  • Общее ядро: два контекста совместно используют небольшое подмножество модели предметной области и базы данных (обычно это не рекомендуется в микросервисах из-за связанности).
  • Клиент-поставщик: один контекст (поставщик) должен предоставлять данные другому (клиенту). Поставщик должен согласовать графики выпуска с заказчиком.
  • Антикоррупционный уровень (ACL): уровень трансляции, который преобразует входящие данные из внешней системы во внутреннюю модель домена клиента, не позволяя внешним моделям загрязнять внутреннюю архитектуру.

2. Тактический план: структурирование базы кода микросервиса

После того как границы сервиса определены с помощью стратегического проектирования, Тактическое проектирование предоставляет набор шаблонов проектирования для структурирования кода в рамках одного микросервиса.

A. Сущности и объекты значений

Внутри микросервиса модели делятся на две категории:

  1. Сущности: объекты, имеющие уникальную идентичность, которая сохраняется с течением времени, даже если их атрибуты меняются. Примеры: Order или Product.
  2. Объекты значений: объекты, которые не имеют уникальной идентичности и полностью определяются своими атрибутами. Они неизменны. Примеры: ShippingAddress или ProductDimensions. Если два объекта-значения имеют одинаковые атрибуты, они считаются равными.

B. Агрегаты и корни агрегатов

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

Например, на схеме выше:

  • В Службе заказов Order является совокупным корнем. Он содержит OrderItem (сущность) и ShippingAddress (объект значения). Внешние службы не могут изменять OrderItem напрямую; они должны вызвать метод в корне Order (например, order.AddItem()), который проверяет, что заказ еще не отправлен.

C. События домена

Событие предметной области – это событие, произошедшее в сфере, которая волнует бизнес-экспертов. Он используется для асинхронной передачи изменений в ограниченных контекстах. Когда агрегат выполняет команду, он публикует событие домена (например, OrderCreated). Другие службы прослушивают это событие и соответствующим образом обновляют свое состояние, обеспечивая конечную согласованность без синхронных зависимостей API.


3. Конкретный пример: служба заказов и служба инвентаризации

Давайте посмотрим на упрощенную реализацию тактического DDD в Go для Службы заказов, показывающую, как совокупный корень координирует бизнес-правила.

package domain

import (
	"errors"
	"time"
)

// Value Object (Immutable, identity-less)
type ShippingAddress struct {
	Street  string
	City    string
	ZipCode string
}

// Entity (Has identity, mutable)
type OrderItem struct {
	ProductID string
	Quantity  int
	Price     float64
}

// Aggregate Root (Enforces transactional boundaries)
type Order struct {
	ID        string
	Items     []OrderItem
	Address   ShippingAddress
	Status    string
	CreatedAt time.Time
}

// NewOrder creates a new Order Aggregate Root
func NewOrder(id string, address ShippingAddress) *Order {
	return &Order{
		ID:        id,
		Items:     []OrderItem{},
		Address:   address,
		Status:    "PENDING",
		CreatedAt: time.Now(),
	}
}

// AddItem enforces business rules before mutating state
func (o *Order) AddItem(productID string, qty int, price float64) error {
	if o.Status != "PENDING" {
		return errors.New("cannot add items to a finalized or cancelled order")
	}
	if qty <= 0 {
		return errors.New("quantity must be greater than zero")
	}
	
	o.Items = append(o.Items, OrderItem{
		ProductID: productID,
		Quantity:  qty,
		Price:     price,
	})
	return nil
}

Когда заказ успешно сохранен, сервис публикует событие в брокере сообщений:

{
  "event_id": "evt_98231",
  "event_type": "OrderCreated",
  "timestamp": "2026-06-26T00:15:00Z",
  "payload": {
    "order_id": "ord_5521",
    "items": [
      { "product_id": "prod_88", "quantity": 2 }
    ]
  }
}

Служба инвентаризации прослушивает это событие, обновляет свой локальный объект StockLevel и завершает поток асинхронно.


4. Стратегическое и тактическое DDD в микросервисах

Фаза/Аспект Стратегический DDD Тактический DDD
Объем Глобальная архитектура системы (макро) Внутри единой кодовой базы сервиса (микро)
Основная цель Нарисуйте четкие, несвязанные границы услуг Моделируйте богатую бизнес-логику и применяйте инварианты
Основные понятия Ограниченные контексты, вездесущий язык, карта контекстов Сущности, объекты значений, агрегаты, события предметной области
Аудитория Архитекторы, менеджеры по продуктам, разработчики Разработчики программного обеспечения, рецензенты кода
Влияние на микросервисы Определяет количество и объем услуг Определяет транзакции базы данных и структуру каталогов

Вывод: сначала начните со стратегического DDD

Применение доменно-ориентированного проектирования к микросервисам — это мощный способ обеспечить соответствие вашей архитектуры структуре вашего бизнеса. Однако команды часто совершают ошибку, уделяя слишком много внимания тактическим шаблонам (например, написанию интерфейсов репозитория и ценных объектов), игнорируя при этом стратегический дизайн.

Если вы не определите границы правильно, никакое количество чистого тактического кода не спасет вашу систему от превращения в распределенный монолит. Всегда начинайте со стратегического картирования — определите свой вездесущий язык, сгруппируйте логику в ограниченные контексты, составьте карту коммуникационных потоков и позвольте границам ваших микросервисов естественным образом возникнуть из этих определений.


Узнайте больше об архитектуре программного обеспечения, шаблонах проектирования и инженерных идеях в блоге Ghaznix →