2014-11-28 4 views
0

У меня есть сценарий, который пытается реорганизовать DDD. У меня есть пакет, который является агрегатом и List of BatchEntries. После создания пакета и добавления BatchEntries, сообщение отправляется отдельным лицам в партии, а статус изменений партии - от запущенных до отправленных.Рефакторинг для управления доменом

Любые идеи о том, как сделать дизайн лучше? В домене есть два агрегата Batch и BatchEntry, причем Batch является агрегированным корнем.

код выглядит следующим образом

public class Batch : EntityBase, IValidatableObject 
{ 
    public int BatchNumber { get; set; } 
    public string Description { get; set; } 
    public decimal TotalValue { get; set; } 
    public bool SMSAlert { get; set; } 
    public int Status { get; set; } 

    private HashSet<BatchEntry> _batchEntries; 
    public virtual ICollection<BatchEntry> BatchEntries 
    { 
     get{ 
      if (_batchEntries == null){ 
       _batchEntries = new HashSet<BatchEntry>(); 
      } 
      return _batchEntries; 
     } 
     private set { 
      _batchEntries = new HashSet<BatchEntry>(value); 
     } 
    } 

    public static Batch Create(string description, decimal totalValue, bool smsAlert) 
    { 
     var batch = new Batch(); 
     batch.GenerateNewIdentity(); 
     batch.Description = description; 
     batch.TotalValue = totalValue; 
     batch.SMSAlert = smsAlert; 
     return batch; 
    } 

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
     // 
    } 
} 

public interface IBatchRepository : IRepository<Batch> 
{ 
    int NextBatchNumber(); 
} 

public class BatchEntry : EntityBase, IValidatableObject 
{ 
    public Guid BatchId { get; set; } 
    public virtual Batch Batch { get; private set; } 
    public decimal Amount { get; set; } 
    public Guid CustomerAccountId { get; set; } 
    public virtual CustomerAccount CustomerAccount { get; private set; } 

    public static BatchEntry Create(Guid batchId, Guid customerAccountId, decimal amount) 
    { 
     var batchEntry = new BatchEntry(); 
     batchEntry.GenerateNewIdentity(); 
     batchEntry.BatchId = batchId; 
     batchEntry.CustomerAccountId = customerAccountId; 
     batchEntry.Amount = amount; 
     return batchEntry; 
    } 

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
     // 
    } 
} 

public interface IBatchEntryRepository : IRepository<BatchEntry>{} 

домен и доменные службы подвергаются с помощью Application Services. Кодекс в прикладных услуг выглядит следующим образом:

//Application Services Code 

public class BatchApplicationService : IBatchApplicationService 
{ 
    private readonly IBatchRepository _batchRepository; 
    private readonly IBatchEntryRepository _batchEntryRepository; 

    public BatchAppService(IBatchRepository batchRepository, IBatchEntryRepository batchEntryRepository) 
    { 
     if (batchRepository == null) throw new ArgumentNullException("batchRepository"); 

     if (batchEntryRepository == null) throw new ArgumentNullException("batchEntryRepository"); 

     _batchRepository = batchRepository; 
     _batchEntryRepository = batchEntryRepository; 
    } 

    public BatchDTO AddNewBatch(BatchDto batchDto) 
    { 
     if (batchDto != null) 
     { 
      var batch = Batch.Create(batchDto.Description, batchDto.TotalValue, batchDto.SMSAlert); 
      batch.BatchNumber = _batchRepository.NextBatchNumber(); 
      batch.Status = (int)BatchStatus.Running; 
      SaveBatch(batch); 
      return batch.Map<BatchDto>(); 
     } 
     else 
     { 
      // 
     } 
    } 

    public bool UpdateBatch(BatchDto batchDto) 
    { 
     if (batchDto == null || batchDto.Id == Guid.Empty) 
     { 
      // 
     } 

     var persisted = _batchRepository.Get(batchDto.Id); 
     if (persisted != null) 
     { 
      var result = false; 
      var current = Batch.Create(batchDto.Description, batchDto.TotalValue, batchDto.SMSAlert); 
      current.ChangeCurrentIdentity(persisted.Id); 
      current.BatchNumber = persisted.BatchNumber; 
      current.Status = persisted.Status; 

      _batchRepository.Merge(persisted, current); 
      _batchRepository.UnitOfWork.Commit(); 

      if (persisted.BatchEntries.Count != 0){ 
       persisted.BatchEntries.ToList().ForEach(x => _batchEntryRepository.Remove(x)); 
       _batchEntryRepository.UnitOfWork.Commit(); 
      } 

      if (batchDto.BatchEntries != null && batchDto.BatchEntries.Any()) 
      { 
       List<BatchEntry> batchEntries = new List<BatchEntry>(); 
       int counter = default(int); 
       batchDTO.BatchEntries.ToList().ForEach(x => 
       { 
        var batchEntry = BatchEntry.Create(persisted.Id, x.CustomerAccountId, x.Amount); 
        batchEntries.Add(batchEntry); 
       }); 
      } 
      else result = true; 
      return result; 
     } 
     else 
     { 
      // 
     } 
    } 

    public bool MarkBatchAsPosted(BatchDto batchDto, int authStatus) 
    { 
     var result = false; 
     if (batchDto == null || batchDto.Id == Guid.Empty) 
     { 
      // 
     } 

     var persisted = _batchRepository.Get(batchDto.Id); 
     if (persisted != null) 
     { 
      var current = Batch.Create(batchDto.Description, batchDto.TotalValue, batchDto.SMSAlert); 
      current.ChangeCurrentIdentity(persisted.Id); 
      current.BatchNumber = persisted.BatchNumber; 
      current.Status = authStatus; 
      _batchRepository.Merge(persisted, current); 
      _batchRepository.UnitOfWork.Commit(); 
      result = true; 
     } 
     else 
     { 
      // 
     } 
     return result; 
    } 

    private void SaveBatch(Batch batch) 
    { 
     var validator = EntityValidatorFactory.CreateValidator(); 
     if (validator.IsValid<Batch>(batch)) 
     { 
      _batchRepository.Add(batch); 
      _batchRepository.UnitOfWork.Commit(); 
     } 
     else throw new ApplicationValidationErrorsException(validator.GetInvalidMessages(batch)); 
    } 
} 

Вопросы:

  1. Где должен BatchStatus Running т.е., Опубликовано быть назначены?
  2. Должен ли метод MarkBatchAsPosted быть определен как меход в пакетной сущности?
  3. Насколько лучше это можно переделать для управляемого доменом дизайна?

ответ

6

Хотя это выглядит просто, я не уверен, что я действительно понимаю ваш домен.

Такие заявления, как

«После того, как Batch создается и добавляется BatchEntries, СУБП отправляется особей в партии и статус изменений пакетных от работает до отправил»

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

Во всяком случае, я не рискнул ответить на ваши 3 вопроса, но есть несколько рекомендаций, вы, кажется, нарушает и их понимание позволит вам придумать свои собственные ответы:

  • Ваш домен страдает от anemia.

  • Агрегаты без полномочий root не должны иметь свой собственный репозиторий, потому что к ним следует обращаться только через корень. Совокупные дети корня должны быть изменены только через их корень (Tell Do not Ask). У вас не должно быть BatchEntryRepository, если EntryRepository не является корневым.

  • Агрегированный корень - это граница транзакции, и только одна должна быть изменена в той же транзакции. Кроме того, совокупные корни должны быть как можно меньше, поэтому вы сохраняете только фрагменты, необходимые для обеспечения инвариантов внутри кластера. В вашем случае добавление/удаление записей в партии, похоже, влияет на статус Batch, поэтому наличие коллекции BatchEntry под номером Batch имеет смысл и позволяет защищать инварианты transactionnaly.

    Примечание: Если было много споров на Batch, например. несколько человек, работающих с одним и тем же экземпляром Batch, добавляя и удаляя BatchEntry экземпляры, тогда вам может понадобиться сделать BatchEntry его собственным агрегированным корнем и использовать условную согласованность, чтобы привести систему в согласованное состояние.

  • Объекты домена обычно должны быть сконструированы с использованием всегда действующего подхода, то есть они никогда не могут быть помещены в недопустимое состояние. Пользовательский интерфейс обычно должен заботиться о проверке ввода пользователем, чтобы избежать отправки неправильных команд, но домен может просто на вас наброситься. Таким образом, validator.IsValid<Batch>(batch) имеет очень мало смысла, если он не проверяет что-то, что Batch не может принудительно выполнить сам по себе.

  • Логика домена не должна протекать в службах приложений и обычно должна быть инкапсулирована в сущности, когда это возможно (услуги домена в противном случае). В настоящее время вы выполняете много бизнес-логики в своей службе приложений, например. if (persisted.BatchEntries.Count != 0){ ... }

  • DDD не является CRUD. Использование тактических шаблонов DDD в CRUD не является необходимым, но это, безусловно, не DDD. DDD - это все о вездесущем языке и моделировании домена. Когда вы видите методы с именем Update... или тонны getter/setters, это обычно означает, что вы делаете это неправильно. DDD лучше всего работает с пользовательским интерфейсом на основе задач, который позволяет сосредоточиться на одной бизнес-операции за раз. Ваш метод UpdateBatch делает слишком много и должен быть разделен на более скучные и гранулированные бизнес-операции.

Надеется, что мой ответ поможет вам совершенствуя свою модель, но я настоятельно советую вам прочитать либо Evans или Vernon ... или оба;)

+0

это довольно хорошо читать. У меня есть много информации, которая поможет в моем рефакторинге. Thanx – mkaris

+0

@mkaris Я рад помочь. Если у вас есть более конкретные вопросы, дайте мне знать. – plalx

+0

Я бы отреагировал на **, что для меня очень мало смысла. Может ли партия быть партией без каких-либо записей? Если нет, то почему пакет автоматически запускается при добавлении записей? ** В бизнес-правиле оговаривается, что пакет создается без позиций, но должен иметь хотя бы описание, batchnumber и totalvalue и статус (Ожидание - от запуска после серьезного думал). Записи добавляются позже на более позднем этапе. Значение партикуляции будет использоваться для проверки того, полностью ли выделена партия. Когда это будет сохранено (обновить записи), статус будет изменен на отправленный. – mkaris

Смежные вопросы