В доменном дизайне было бы нарушением DDD помещать вызовы в репозитории других объектов в объекте домена? - PullRequest
8 голосов
/ 27 февраля 2009

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

public decimal CaculateBatchTotal(VendorApplicationBatch batch)
{
     IList<VendorApplication> applications = AppRepo.GetByBatchId(batch.Id);

     if (applications == null || applications.Count == 0)
          throw new ArgumentException("There were no applications for this batch, that shouldn't be possible");
     decimal total = 0m;
     foreach (VendorApplication app in applications)
          total += app.Amount;
     return total;
}

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

Мои вопросы, таким образом:

  1. Где бы вы положили этот код?
  2. Вы бы разбили эту функцию?
  3. Куда бы это отнес кто-то, кто придерживается строгого доменного управления?
  4. Почему?

Спасибо за ваше время.

Редактировать. Примечание. В этом случае нельзя использовать ORM, поэтому я не могу использовать решение для отложенной загрузки.

Редактировать. Примечание 2. Я не могу изменить конструктор, чтобы он принимал параметры из-за того, как потенциальный слой данных создает объекты домена с помощью отражения (не моя идея).

Редактировать Примечание 3: Я не верю, что пакетный объект должен иметь возможность составлять только любой список приложений, похоже, он должен иметь возможность только суммировать приложения, которые находятся в этом конкретном пакете. В противном случае для меня более разумно оставить функцию в классе обслуживания.

Ответы [ 5 ]

5 голосов
/ 27 февраля 2009

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

Что вы можете сделать, это либо позволить службе предоставить объекту домена соответствующую информацию, либо иметь делегата в объекте домена, который устанавливается службой или в конструкторе.

public DomainObject(delegate getApplicationsByBatchID)
{
    ...
}
5 голосов
/ 27 февраля 2009

Я не эксперт по DDD, но помню статью великого Джереми Миллера, которая ответила на этот вопрос для меня. Обычно вам нужна логика, связанная с вашими объектами домена - внутри этих объектов, но ваш класс обслуживания будет выполнять методы, содержащие эту логику. Это помогло мне внедрить специфичную для домена логику в классы сущностей и сделать мои классы обслуживания менее громоздкими (так как я обнаружил, что в классах обслуживания, как вы упомянули, используется много логики)

Редактировать: Пример

Я использую корпоративную библиотеку для простой проверки, поэтому в классе сущности я установлю атрибут следующим образом:

 [StringLengthValidator(1, 100)]
 public string Username {
     get { return mUsername; }
     set { mUsername = value; }
 }

Сущность наследуется от базового класса, который имеет следующий метод "IsValid", который обеспечит соответствие каждого объекта критериям валидации

     public bool IsValid()
     {
         mResults = new ValidationResults();
         Validate(mResults);

         return mResults.IsValid();
     }

     [SelfValidation()]
     public virtual void Validate(ValidationResults results)
     {
         if (!object.ReferenceEquals(this.GetType(), typeof(BusinessBase<T>))) {
             Validator validator = ValidationFactory.CreateValidator(this.GetType());
             results.AddAllResults(validator.Validate(this));
         }
         //before we return the bool value, if we have any validation results map them into the
         //broken rules property so the parent class can display them to the end user
         if (!results.IsValid()) {
             mBrokenRules = new List<BrokenRule>();
             foreach (Microsoft.Practices.EnterpriseLibrary.Validation.ValidationResult result in results) {
                 mRule = new BrokenRule();
                 mRule.Message = result.Message;
                 mRule.PropertyName = result.Key.ToString();
                 mBrokenRules.Add(mRule);
             }
         }
     }

Далее нам нужно выполнить этот метод «IsValid» в методе сохранения класса обслуживания, например:

 public void SaveUser(User UserObject)
 {
     if (UserObject.IsValid()) {
         mRepository.SaveUser(UserObject);
     }
 }

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

2 голосов
/ 27 февраля 2009

Почему бы не передать IList <VendorApplication> в качестве параметра вместо VendorApplicationBatch? Код вызова для этого предположительно будет исходить от службы, которая будет иметь доступ к AppRepo. Таким образом, ваш доступ к хранилищу будет там, где он есть, в то время как функция вашего домена может оставаться в блаженном неведении о том, откуда эти данные пришли.

1 голос
/ 27 февраля 2009

Насколько я понимаю (недостаточно информации, чтобы понять, является ли это правильным дизайном) VendorApplicationBatch должен содержать отложенный загруженный IList внутри объекта домена, а логика должна оставаться в домене.

Например (код воздуха):

public class VendorApplicationBatch  {

    private IList<VendorApplication> Applications {get; set;};   

    public decimal CaculateBatchTotal()
    {
        if (Applications == null || Applications.Count == 0)
            throw new ArgumentException("There were no applications for this batch, that shouldn't be possible");

        decimal Total = 0m;
        foreach (VendorApplication App in Applications)
            Total += App.Amount;
       return Total;
    }
}

Это легко сделать с помощью ORM, такого как NHibernate, и я думаю, что это будет лучшим решением.

0 голосов
/ 23 декабря 2009

Мне кажется, что ваш CalculateTotal является службой для коллекций VendorApplication и что возвращение коллекции VendorApplication для Batch естественно подходит как свойство класса Batch. Таким образом, некоторые другие службы / контроллеры / что-либо другое извлекают соответствующую коллекцию VendorApplication из пакета и передают их в службу VendorApplicationTotalCalculator (или что-то подобное). Но это может нарушить некоторые правила корневого сервиса агрегирования DDD или что-то такое, чего я не знаю (новичок DDD).

...