Мутирующий vs чистый
Domain Service
Введение
При проектировании по DDD возникает вопрос: кто отвечает за изменение состояния агрегатов? Следует ли изменять агрегаты прямо в доменном сервисе или оставить сервис «чистым», а логику изменений вынести в слой приложения (use case)?

Разберёмся на примере: у нас есть курьеры и заказы, и нам нужно выбрать подходящего курьера для доставки. Основная проблема заключается в том, делать ли сервис как чистую функцию или допускать изменение состояния агрегатов внутри него.
Вариант 1: Доменный сервис мутирует объекты
Domain service
func (s *dispatcher) Dispatch(order *Order, couriers []*Courier) (*Courier, error) {
	for _, c := range couriers {
		if c.HasFreeSlot() && c.IsNear(order.Location) {
			// Мутируем агрегаты внутри сервиса
			err := order.Assign(c.ID)
			if err != nil {
				return nil, err
			}
			err = c.TakeOrder(order)
			if err != nil {
				return nil, err
			}
			return c, nil
		}
	}
	return nil, ErrCourierNotFound
}
Плюсы:
  • Вся логика в одном месте.
  • Application слой просто вызывает Dispatch и всё работает.

Минусы:
  • Доменный сервис имеет сайд-эффекты.
  • Тестировать выбор и мутацию по отдельности сложно.
Вариант 2: Доменный сервис как чистая функция
Domain service
func (s *dispatcher) FindBestCourier(order *Order, couriers []*Courier) (*Courier, error) {
	for _, c := range couriers {
		if c.HasFreeSlot() && c.IsNear(order.Location) {
			return c, nil
		}
	}
	return nil, ErrCourierNotFound
}
Application слой (use case):
courier, err := dispatcher.FindBestCourier(order, couriers)
if err != nil {
	return err
}

err = order.Assign(courier.ID)
if err != nil {
	return err
}

err = courier.TakeOrder(order)
if err != nil {
	return err
}

Плюсы:
  • Сервис легко тестировать — он ничего не мутирует.
  • Чёткое разделение: сервис выбирает, use case меняет состояние.

Минусы:
  • Нужно помнить, что после FindBestCourier нужно ещё вызвать Assign и TakeOrder.
  • Повышается связность в application слое.
Какой вариант выбрать?
Domain service мутирующий агрегаты, если:
  • Хотите скрыть детали, как именно работает логика.
  • Нужно сделать «всё в одном вызове».

Domain service как чистая функция, если:
  • Важна прозрачность и контроль.
  • Требуется тестировать выбор и действия отдельно.
Заключение
DDD не запрещает мутировать агрегаты в доменном сервисе. Главное — не смешивать ответственность и сохранять чистую архитектуру.

  • Хотите простоты — мутируйте внутри.
  • Хотите гибкости — разделяйте.
Эта и другие темы рассматриваются
на курсах:
Скоро начало курса, успей попасть!
Скоро начало курса, успей попасть!
Скоро начало курса, успей попасть!
Понравилась статья? Поделись в соцсетях!