Как следует обрабатывать транзакции в контексте DDD

В контексте DDD, где должна начинаться и заканчиваться транзакция, когда вы имеете дело с событиями домена?

Infrastructure.Layer имеет реализацию UoW

/// => configured via DI as Per Request
class UnitOfWork : IUnitOfWork
{
     ITransaction _transaction;
     static ISessionFactory _factory; 
     ISession _session

     UnitOfWork()
     {
         _session = _factory.OpenSession();
         _transaction = _session.BeginTransaction();  ///=> start transaction 
     }

     void Commit()
     {
          try
             _transaction.Commit();
          catch
            _transaction.Rollback(); 
          finally
            Dispose();
     } 
}

Application.Layer UseCase Handler

class SomeAppServiceUseCaseHandler : IUseCaseHandler
{
      IUnitOfWork _uow;
      ISomeRepo _repo;

      AppService(IUnitOfWork uow, ISomeRepo repo)
      {
          _uow = uow;
          _repo = repo;
      }

      void UseCaseHandler(Request request)
      {

         SomeAggregate agg = _repo.GetAggregate(request.Id) 

                       agg.DoSomethingToChangeState();

         _repo.UpdateAgg(agg);

         _uow.Commit(agg);  ///=> commit changes for this transaction success
      }
}

и в Domain.Layer есть метод, который также добавит Domain.Event в список событий домена для агрегата.

SomeAggregate : AggregateRoot
{
   DoSomethingToChangeState()
   {
       .... do something

       var someObject;
       base.AddEvent(new SomethingHappenedEvent(someObject)));
   }
}

Application.Layer имеет обработчики Domain.Event

class SomethingHappenedEventHander : Handler<SomethingHappenedEvent>
{
    IRepo repo;
    IUnitOfWork _uow;

    DomainEventHander(IRepo repo, IUnitOfWork uow)
    {
        _repo = repo;
        _uow= uow;
    }

    HandleEvent(someObject)
    {
         AnotherAggregate agg = new AnotherAggregate ();
                          agg.DoSomeCommand(someObject);

         _repo.Create(agg);
         _uow.Commit();  ///=> commit changes for same transaction fail, should rollback prev transaction as well
    }
}

Я чувствую, что это неправильно

  1. Кто должен опубликовать мероприятие? из того, что я вижу, UoW должен делать это в методе Commit (), но я не думаю, что это правильно, я полагал, что UoW не должен этого делать, но я не вижу, кто еще мог бы.

  2. Если в какой-то момент в цепочке что-то выходит из строя, я уже зафиксировал некоторые данные, чего, скорее всего, я бы не стал делать, если что-то в процессе выйдет из строя.

Итак, как следует правильно справиться с этими двумя ситуациями?


2
592
1

Ответ:

Решено

Как заявил Константин, для каждой команды следует обновлять только один агрегат. Почему? Потому что, обновляя более одного агрегата в транзакции, вы уменьшаете пропускную способность системы. Чем больше границы транзакции вашей базы данных, тем больше вероятность того, что вы столкнетесь с конфликтом при записи данных, поэтому вы хотите, чтобы ваши транзакции были как можно более детализированными.

По поводу ваших вопросов:

  1. UnitOfWork, безусловно, может сохранить изменения. В качестве альтернативы ваш класс Repo может сделать то же самое с новым методом IRepo.Save (Aggregate):

    _repo.Save(agg);
    
  2. Ваше беспокойство справедливо. Вы можете (и, возможно, должны) вывести свой UnitOfWork за пределы уровня Handler / UseCaseHandler, чтобы у вас не было этого шаблонного кода для фиксации UoW в каждом обработчике. Затем вы можете сохранить оба ваших агрегата в одной транзакции базы данных. Лучшим подходом было бы использование архитектуры, позволяющей восстанавливаться после сбоев. Ты мог бы:

    1. Обработайте команду и создайте события в хранилище событий. (UoW сделает это)
    2. Опрашивайте о новых событиях в хранилище событий и публикуйте их в очереди.
    3. Считайте события из очереди и отправьте их любому зарегистрированному обработчику.
    4. Сохраните новые события в хранилище событий (снова с помощью UoW), и процесс повторится.

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