Как шаблон «Единица работы» учитывает ссылки на новые агрегаты? - PullRequest
3 голосов
/ 20 января 2011

Фон

Насколько я понимаю, шаблон единиц работы (UoW) по существу обеспечивает семантику транзакций. Другими словами, учитывая, что область агрегатов сохраняется в репозиториях, класс UoW позволяет потребителям домена регистрировать вызовы методов репозитория в элементарной операции. Скажем, у нас есть:

interface IAggregate<TKey> {
    TKey Id { get; }
}

interface IRepository<TEntity, in TKey> where TEntity : IAggregate<TKey> {
    TEntity Get(TKey id);
    void Save(TEntity entity);
    void Remove(TEntity entity);
}

interface IUnitOfWork {
    void RegisterSave<TEntity>(TEntity entity);
    void RegisterRemove<TEntity>(TEntity entity);
    void RegisterUnitOfWork(IUnitOfWork uow);
    void Commit();
    void Rollback();
}

Предположим, что реализации IRepository используют реляционную базу данных, а реализация IUnitOfWork.Commit просто устанавливает транзакцию с базой данных и продолжает вызывать Save или Remove в соответствующих экземплярах IRepository для всех операции, которые были зарегистрированы. Я бы сказал, что я обрисовал в общих чертах стандартную прямую интерпретацию шаблонов Aggregate Root, Repository и UoW (NHibernate / EF и всей их раздутой славы).

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

class User : IAggregate<int> {
  int Id { get; private set; }
}

class Blog : IAggregate<int> {
  int Id { get; private set; }
  int AuthorUserId { get; set; }
}

Вопрос

Учитывая вышеупомянутое разделение проблем и интерпретацию границ агрегата, как можно было бы обеспечить поддержку транзакций потребителям, которым необходимо создать агрегат и сохранить его сгенерированный в хранилище идентификатор в другом агрегате? Например. как я могу создать User и Blog транзакционно с Blog.UserId, установленным на User.Id?

Я придумала несколько ответов (помечена вики сообщества), но все равно выкладываю здесь свой вопрос, чтобы получить обратную связь и получить больше ответов.

Ответы [ 3 ]

1 голос
/ 23 января 2011

Учитывая приведенное выше разделение интересов и интерпретацию границ агрегата, как можно было бы обеспечить поддержку транзакций для потребителей, которым необходимо создать агрегат и сохранить его сгенерированный в хранилище идентификатор в другом агрегате?Например, как я могу создать пользователя и блог транзакционно с Blog.UserId, установленным в User.Id?

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

Если есть такая необходимость - вы неправильно моделируете агрегаты.


Просто публикуете несколько быстрых комментариев, которые могут быть полезны ...

interface IAggregate<TKey> {
    TKey Id { get; }
}

Интерфейсыследует использовать для определения поведения (ролей), а не того, что будет содержать реализующий класс (в данном случае знание типа ключа).

Не могу Google достаточно быстро найти правильное объяснение, почему именно так ... Хотя попробую позже.

interface IRepository<TEntity, in TKey> where TEntity : IAggregate<TKey> {
  TEntity Get(TKey id);
  void Save(TEntity entity);
  void Remove(TEntity entity);
}

Избегайте использования универсальных репозиториев .

interface IUnitOfWork {
  void RegisterSave<TEntity>(TEntity entity);
  void RegisterRemove<TEntity>(TEntity entity);
  void RegisterUnitOfWork(IUnitOfWork uow);
  void Commit();
  void Rollback();
}

Избегайте использования единицы работы (это решает вашу проблему).

interface IAggregate<TSelf> where TSelf : IAggregate<TSelf>
{
    IKey<TSelf> Id { get; }
}

Продолжайте учиться.Достаточно скоро Вы перестанете злоупотреблять интерфейсами и генериками.:)

0 голосов
/ 20 января 2011

Похоже, что одно из следующих ограничений шаблона должно быть ослаблено:

  • Репозиторий отвечает за генерацию агрегированных идентификаторов. Вместо этого должен использоваться естественный ключ.
  • UoW не может зарегистрировать произвольных Action делегатов. Вместо этого разрешите UoW регистрировать произвольно Actions.
  • Межагрегатные отношения объективируются как свойства идентификатора. Вместо этого используйте агрегированные свойства, такие как:

partial class Blog : IAggregate<int> { 
  User Author { get; set; } 
}

Так я неправильно истолковал шаблоны? Или они на самом деле ограничены в этом отношении?

0 голосов
/ 20 января 2011

Должен ли тип идентификатора быть умнее ссылочного типа?Таким образом, когда хранилище обновляет идентификатор, он может быть доступен по ссылке другим агрегатом.Например:

interface IKey<TAggregate> : IEquatable<IKey<TAggregate>>
{
    /* should provide a ctor that accepts a string */

    TAggregate GetAggregateType();
    string ToString();
    bool IsAssigned { get; }
}

interface IAggregate<TSelf> where TSelf : IAggregate<TSelf>
{
    IKey<TSelf> Id { get; }
}

interface IRepository<TAggregate> where TAggregate : IAggregate<TAggregate>
{
    TAggregate Get(IKey<TAggregate> id);
    void Save(TAggregate entity);
    void Remove(TAggregate entity);
}

class User : IAggregate<User>
{
    public IKey<User> Id { get; private set; }
}

class Blog : IAggregate<Blog>
{
    public IKey<Blog> Id { get; private set; }
    public IKey<User> Author { get; private set; }
}

Реализация IKey<TAggregate> предоставляется в том же пакете, что и реализации IRepository<TAggregate>.

...