DDD - переход состояния объекта - PullRequest
8 голосов
/ 11 февраля 2010

рассмотрим следующий упрощенный пример:

public class Ticket
{
   public int Id;
   public TicketState State;

   public Ticket()
   {
      // from where do I get the "New" state entity here? with its id and name
      State = State.New;
   }

   public void Finished()
   {
      // from where do I get the "Finished" state entity here? with its id and name          
      State = State.Finished;
   }
}

public class TicketState
{
   public int Id;
   public string Name;
}

Состояние класса используется непосредственно в билете объекта домена. Позже в жизненном цикле билета могут быть установлены другие состояния.

Билет сохраняется в таблице Ticket, а также в TicketState. Таким образом, в БД билет будет иметь внешний ключ к таблице состояния заявки.

Когда я устанавливаю соответствующее состояние в моей сущности, как мне загрузить экземпляр состояния из БД? Нужно ли вводить хранилище в сущность? Нужно ли использовать каркас типа замка для такого случая? Или есть лучшие решения, может быть, передать государство извне?

public class Ticket
{
   //...
   public ITicketStateRepository stateRep; //<-- inject

   public Ticket()
   {
      State = stateRep.GetById(NEW_STATE_ID);
   }
   //...
}

Есть ли лучшая практика? До сих пор я не использовал какую-либо инфраструктуру внедрения зависимостей или что-то еще и держал все постоянные вещи вне моего домена

Другой подход:

public class Ticket
{
   //...

   public Ticket(NewTicketState newTicketState)
   {
      State = newTicketState;
   }
   public void Finished(FinishedTicketState finishedTicketState)
   {
      State = finishedTicketState;
   }
   //...
}

Ответы [ 3 ]

4 голосов
/ 11 февраля 2010

Билет не будет иметь ссылку на хранилище. Он будет иметь отношение один к одному с TicketState, а TicketRepository просто сделает JOIN и отобразит значения в Ticket.

Когда я создаю объекты модели, я обычно не информирую их о том, являются ли они постоянными или нет, поэтому они не внедряются в репозиторий. Репозиторий обрабатывает все операции CRUD.

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

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

Обновление:

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

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

1 голос
/ 11 февраля 2010

Этот ответ, вероятно, следует из ответа Даффимо.

В представлении DDD о мире ваш TicketState является сущностью, являющейся частью совокупности заявок (где заявка является корнем совокупности).

Исходя из этого, ваш TicketRepository имеет дело как с Билетами, так и с TicketStates.

Когда вы извлекаете Билет из слоя постоянства, вы затем разрешаете своему TicketRepository получать состояние из БД и правильно устанавливать его в Билете.

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

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

0 голосов
/ 11 февраля 2010

Для меня простая пара ключ-значение, которая представляет состояние в базе данных (или любой другой персистентной среде), не должна моделироваться как таковая в домене. В домене я бы сделал перечисление TicketState и возложил на ITicketRepository ответственность за то, чтобы сопоставить это с требованиями схемы базы данных.

В репозитории тикетов вы можете иметь кеш идентификаторов состояний тикетов, привязанный к TicketState, который лениво загружается в статическую переменную (всего один подход) из базы данных. Хранилище билетов будет сопоставлять значение Ticket.State с идентификаторами из этого кэша для вставок / обновлений.

namespace Domain {
  public class Ticket {
    public Ticket() { State = TicketStates.New; }
    public void Finish() { State = TicketStates.Finished; }
    public TicketStates State {get;set;}
  }

  public enum TicketState { New, Finished }
}

namespace Repositories {
  public class SqlTicketRepository : ITicketRepository {
    public void Save(Ticket ticket) {
      using (var tx = new TransactionScope()) { // or whatever unit of work mechanism
        int newStateId = TicketStateIds[ticket.State];
        // update Ticket table with newStateId
      }
    }
  }

  private Dictionary<TicketState, int> _ticketStateIds;
  protected Dictionary<TicketState, int> TicketStateIds{
    get {
      if (_ticketStateIds== null) 
        InitializeTicketStateIds();
      return _ticketStateIds;
    }
  }

  private void InitializeTicketStateIds() {
    // execute SQL to get all key-values pairs from TicketStateValues table
    // use hard-coded mapping from strings to enum to populate _ticketStateIds;
  }
}
...