在域驱动设计中,在域对象中调用其他对象的repostiories会违反DDD吗?

我正在重构一个正在包装的项目的代码,最后我把很多业务逻辑放到服务类而不是域对象中。 此时,大多数域对象仅是数据容器。 我决定在服务对象中编写大部分业务逻辑,然后将所有内容重构为更好,更可重用和更易读的形状。 这样我就可以决定应该将哪些代码放入域对象中,以及将哪些代码分解为自己的新对象,以及应该在服务类中保留哪些代码。 所以我有一些代码:

public decimal CaculateBatchTotal(VendorApplicationBatch batch) { IList 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. 你会打破这个function吗?
  3. 那些遵循严格的领域驱动设计的人会把它放在哪里?
  4. 为什么?

谢谢你的时间。

编辑注意:不能在这个上使用ORM,所以我不能使用延迟加载解决方案。

编辑注意2:我无法改变构造函数以获取参数,因为将要使用reflection(不是我的想法)实现数据层对象的实例化。

编辑注意3:我不相信批处理对象应该只能计算任何应用程序列表,看起来它应该只能处理该特定批处理中的应用程序。 否则,将函数保留在服务类中对我来说更有意义。

您甚至不应该从域对象访问存储库。

您可以做的是让服务为域对象提供适当的信息,或者在域对象中具有由服务或构造函数设置的委托。

 public DomainObject(delegate getApplicationsByBatchID) { ... } 

我不是DDD的专家,但我记得伟大的杰里米·米勒的一篇文章为我回答了这个问题。 您通常需要与域对象相关的逻辑 – 在这些对象内部,但您的服务类将执行包含此逻辑的方法。 这有助于我将特定于域的逻辑推入实体类,并使我的服务类不那么笨重(因为我发现自己在服务类中添加了很多逻辑,就像你提到的那样)

编辑:示例

我使用企业库进行简单validation,因此在实体类中我将设置如下属性:

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

该实体inheritance自具有以下“IsValid”方法的基类,该方法将确保每个对象满足validation条件

  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))) { 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(); 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); } } 

更复杂的示例可能是银行帐户。 存款逻辑将存在于帐户对象内,但服务类将调用此方法。

为什么不将IList 作为参数而不是VendorApplicationBatch传入? 这个调用代码可能来自一个可以访问AppRepo的服务。 这样,您的存储库访问将在它所属的位置上升,而您的域function可以保持对该数据来源的无知。

据我所知(没有足够的信息知道这是否是正确的设计)VendorApplicationBatch应该在域对象中包含一个延迟加载的IList,逻辑应该保留在域中。

例如(航空代码):

 public class VendorApplicationBatch { private IList 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; } } 

使用像NHibernate这样的ORM很容易做到这一点,我认为这将是最好的解决方案。

在我看来,您的CalculateTotal是VendorApplication集合的服务,并且返回Batch的VendorApplication集合自然地作为Batch类的属性。 因此,一些其他服务/控制器/无论从批处理中检索适当的VendorApplication集合并将它们传递给VendorApplicationTotalCalculator服务(或类似的东西)。 但这可能会打破一些DDD聚合根服务规则或某些我不知道的事情(DDD新手)。