Вам бонус- начислено 1 монета за дневную активность. Сейчас у вас 1 монета

Трансакции где применять в репозиториях или сервисах в контексте DDD

Лекция



Вопрос о том, где управлять транзакциями в архитектуре, построенной по принципам Domain-Driven Design, возникает практически у каждого разработчика, когда он начинает отходить от простого CRUD-подхода и строить модель предметной области. На первый взгляд может показаться, что транзакция — это просто техническая деталь работы с базой данных, и значит ей место в репозитории, ведь именно он занимается сохранением данных. Однако в DDD транзакция — это не только технический механизм, а прежде всего граница целостности бизнес-операции, и именно это определяет ее правильное расположение в архитектуре.

В DDD система делится на несколько уровней: доменная модель, инфраструктура и прикладной (сервисный) слой. Репозиторий относится к инфраструктуре и выполняет строго ограниченную роль — он предоставляет интерфейс для сохранения и получения агрегатов, скрывая детали хранения. Он не знает, что за бизнес-операция происходит, не понимает контекст use-case и не принимает решений о том, какие изменения должны быть атомарными. Его задача — взять агрегат и записать его в хранилище.

Сервисный слой, напротив, как раз и существует для описания сценариев использования системы. Именно здесь определяется, что пользователь делает с точки зрения бизнеса: оформляет заказ, переводит деньги, регистрируется, обновляет профиль. Эти сценарии часто включают несколько шагов, затрагивают несколько агрегатов и требуют, чтобы вся операция либо завершилась полностью, либо не произошла вовсе. Другими словами, именно здесь появляется понятие атомарности как бизнес-требования. Поэтому границы транзакции естественным образом совпадают с границами use-case, а значит управляться они должны на уровне сервисов.

Когда транзакция помещается внутрь репозитория, она начинает отражать не бизнес-операцию, а отдельное техническое действие — например, один вызов метода save. В простых случаях это не создает проблем, если речь идет о сохранении одного агрегата целиком. Представим, что пользователь меняет свой email. Вся операция ограничена одним агрегатом User, и сохранение этого агрегата действительно может быть выполнено атомарно внутри метода репозитория. В таком сценарии локальная транзакция внутри репозитория не нарушает модель, потому что граница агрегата и граница транзакции совпадают.

Однако как только use-case затрагивает более одного агрегата, ситуация меняется. Например, создание заказа и списание средств со счета — это уже две разные сущности предметной области, два агрегата, каждый из которых имеет собственный репозиторий. Если каждый репозиторий будет открывать и завершать транзакцию самостоятельно, то бизнес-операция распадется на две независимые транзакции. В случае ошибки между ними система окажется в неконсистентном состоянии: заказ может быть создан, а деньги не списаны, или наоборот. Это уже нарушение инвариантов предметной области, и исправить его задним числом намного сложнее, чем изначально правильно определить границы транзакции.

Трансакции где применять в репозиториях или сервисах в контексте DDD

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

Иногда путаница возникает из-за того, что современные ORM и библиотеки реализуют паттерн Unit of Work и сами управляют транзакциями при сохранении сущностей. Это может создавать ощущение, что транзакции «живут» в репозитории. На практике же это лишь техническая реализация механизма фиксации изменений, а решение о том, когда начинать и завершать транзакцию, по-прежнему должно приниматься на уровне сервиса или приложения. Инфраструктура может помогать, но не должна определять границы бизнес-операции.

В DDD (Domain-Driven Design) транзакции — это не про хранение данных, а про управление консистентностью бизнес-операций. Поэтому правильное место для них — это уровень приложения (Application / Service Layer), а не слой репозиториев.

Короткий ответ

Транзакции следует открывать и управлять ими на уровне сервисного слоя (Application Service)
Не в репозиториях

Почему именно сервисный слой?

В DDD есть разделение ответственности:

1. Репозиторий (Repository)

  • Отвечает только за доступ к данным

  • Не знает ничего о бизнес-операциях

  • Не должен управлять транзакциями

Его задача: save(), find(), delete()

2. Сервисный слой (Application Service)

  • Оркестрирует бизнес-операцию

  • Вызывает несколько репозиториев

  • Управляет жизненным циклом транзакции

Именно здесь понятно:

  • где начинается бизнес-операция

  • где она заканчивается

  • что должно быть атомарным

Пример

Допустим, есть use-case:
“Создать заказ и списать деньги со счета”

Это одна бизнес-операция → одна транзакция.

Правильный подход (транзакция в сервисе)

class OrderService {
   public function createOrder(CreateOrderCommand $cmd) { 
      $this->transactionManager->begin();
       try {
         $order = Order::create($cmd->data);
         $this->orderRepository->save($order); 
         $this->paymentService->charge($cmd->userId, $cmd->amount);
         $this->transactionManager->commit();
     } catch (\Throwable $e) { 
       $this->transactionManager->rollback();
       throw $e; 
  } 
}
 } 

Плохой подход (транзакции в репозитории)

class OrderRepository {
    public function save(Order $order) 
    {
     $this->beginTransaction(); //  плохо
    // save 
      $this->commit(); 
    }
} 

Почему плохо:

  • если нужно сохранить 2 агрегата, будет 2 разные транзакции

  • нарушается атомарность

  • невозможно управлять целостностью бизнес-операции

Как это связано с агрегатами

В DDD есть правило:

Одна транзакция = один агрегат (в идеале)

Но на практике:

  • иногда операция затрагивает несколько агрегатов

  • тогда сервис координирует все в одной транзакции

Исключения

Иногда допустимы:

Unit of Work

  • реализуется инфраструктурно

  • но используется из сервисного слоя

Domain Events + eventual consistency

  • тогда транзакция ограничена одним агрегатом

  • остальное через события

Итоговая архитектура

Трансакции где применять в репозиториях или сервисах в контексте DDD

Когда допустимо делать транзакции в репозитории в контексте DDD

допустимо, если:

  1. операция работает только с одним агрегатом

  2. и это атомарный save()

например:

UserRepository::save(user) 

НО как только:

  • несколько агрегатов

  • несколько репозиториев

  • бизнес-процесс

транзакция должна быть выше — в сервисе

Давай разберем на конкретном примере, чтобы стало максимально понятно.

Что значит

«операция работает только с одним агрегатом»

В DDD агрегат (Aggregate) — это кластер сущностей с одним корнем (Aggregate Root), который гарантирует консистентность.

Пример агрегата:

Order (Aggregate Root) 
├── OrderItem 
├── ShippingAddress 
└── PaymentInfo 

Все это — ОДИН агрегат Order

Что значит

«атомарный save()»

Это значит:

Ты сохраняешь один агрегат целиком
и это происходит одним действием (одной транзакцией)

Пример — допустимый случай транзакции в репозитории

Use-case:

Пользователь обновляет свой профиль

Это:

  • 1 агрегат → User

  • одна операция → save(user)

Агрегат

class User {
 private string $email; 
 private string $name; 
  public function changeEmail(string $email) {
     // domain rules
     if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        throw new DomainException("Invalid email");
     }
     $this->email = $email;
 }
} 

Репозиторий (с локальной транзакцией — допустимо)

class UserRepository { 
   public function save(User $user) { 
      $this->db->transaction(function() use ($user) {
         // UPDATE users SET ... 
      });
 } 
} 

тут все нормально, потому что:

  • сохраняется один агрегат

  • операция атомарная

  • нет координации с другими репозиториями

Сервис

class UserService {
    public function changeEmail(int $userId, string $email)
    {
     $user = $this->repo->find($userId);
     $user->changeEmail($email);
     $this->repo->save($user);
     }
 } 
сервис не управляет транзакцией, потому что она и так атомарная

Пример, когда ТАК делать НЕЛЬЗЯ

Use-case:

Создать заказ и списать деньги

Это уже:

  • агрегат Order

  • агрегат Account

два агрегата

Плохой вариант (транзакции в репозиториях)

$orderRepo->save($order);
 // транзакция 1
$accountRepo->withdraw($money);
 // транзакция 2 

если второе упадет — заказ уже создан

Правильный вариант

$this->transaction->begin();
$orderRepo->save($order); 
$accountRepo->withdraw($money);
$this->transaction->commit(); 

транзакция на уровне сервиса

Простое правило

Можно в репозитории

если:

  • затрагивается 1 агрегат

  • это одна операция сохранения

Нельзя в репозитории

если:

  • несколько агрегатов

  • несколько репозиториев

  • бизнес-процесс (use-case)

Еще примеры

можно в репозитории

  • UserRepository::save(user)

  • OrderRepository::save(order)

  • CartRepository::save(cart)

нельзя — нужно в сервисе

  • создать заказ + списать деньги

  • зарегистрировать пользователя + отправить email + создать профиль

  • перевести деньги между счетами

Выводы

если операция — это просто сохранение одного агрегата как единого целого,
то транзакция может быть инкапсулирована внутри репозитория

Но:

если операция — это бизнес-процесс,
то транзакция должна быть на уровне сервисного слоя

✔ Транзакции в DDD размещаются на уровне сервисов (Application Layer)
✔ Репозитории должны быть тупыми (CRUD)
✔ Сервис управляет атомарностью бизнес-операции

Таким образом, ответ на вопрос о том, где размещать транзакции, зависит от того, что именно мы хотим защитить их границами. Если речь идет о простом сохранении одного агрегата, допустимо инкапсулировать техническую транзакцию внутри репозитория. Но как только появляется полноценный бизнес-сценарий, включающий несколько действий или агрегатов, транзакция должна подниматься на уровень сервисного слоя и охватывать весь use-case целиком. Это соответствует главному принципу DDD: технические детали подчиняются модели предметной области, а не наоборот.

В итоге можно сформулировать простое и практичное правило: транзакция должна совпадать с границей бизнес-операции. А место, где описывается бизнес-операция в DDD, — это сервисный слой. Поэтому именно там транзакции и должны управляться в большинстве реальных систем.

Вау!! 😲 Ты еще не читал? Это зря!

создано: 2026-02-13
обновлено: 2026-02-13
8



Рейтиг 9 of 10. count vote: 2
Вы довольны ?:


Поделиться:
Пожаловаться

Найди готовое или заработай

С нашими удобными сервисами без комиссии*

Как это работает? | Узнать цену?

Найти исполнителя
$0 / весь год.
  • У вас есть задание, но нет времени его делать
  • Вы хотите найти профессионала для выплнения задания
  • Возможно примерение функции гаранта на сделку
  • Приорететная поддержка
  • идеально подходит для студентов, у которых нет времени для решения заданий
Готовое решение
$0 / весь год.
  • Вы можите продать(исполнителем) или купить(заказчиком) готовое решение
  • Вам предоставят готовое решение
  • Будет предоставлено в минимальные сроки т.к. задание уже готовое
  • Вы получите базовую гарантию 8 дней
  • Вы можете заработать на материалах
  • подходит как для студентов так и для преподавателей
Я исполнитель
$0 / весь год.
  • Вы профессионал своего дела
  • У вас есть опыт и желание зарабатывать
  • Вы хотите помочь в решении задач или написании работ
  • Возможно примерение функции гаранта на сделку
  • подходит для опытных студентов так и для преподавателей

Комментарии


Оставить комментарий
Если у вас есть какое-либо предложение, идея, благодарность или комментарий, не стесняйтесь писать. Мы очень ценим отзывы и рады услышать ваше мнение.
To reply

Объектно-ориентированное программирование ООП

Термины: Объектно-ориентированное программирование ООП