【问题标题】:Anemic domain model from the aspect-oriented programming point of view. Yet anemic?从面向方面编程的角度来看贫血的领域模型。还是贫血?
【发布时间】:2013-01-11 10:36:17
【问题描述】:

假设您有一个贫血的域模型 (ADM):

public class Employee
{
    public Employee() 
    {
        _roles = new List<Role>();
    }

    private IList<Role> _roles;

    public Guid Id { get; set; }
    public string Name { get; set; }

    public IList<Roles> Roles { get { return _roles; } }
}

public class EmployeeManager
{
    public Employee GetByName(string name) 
    {
        Contract.Requires(name != null);

        return repositoryOfEmployeesInstance.GetByName(name);
    }

    public void AddEmployee(string name) 
    {
        Contract.Requires(name != null);

        // The unique identifier will be generated by the OR/M behind the scenes...
        repositoryOfEmployeesInstance.Add(new Employee { Name = "Matias" });
    }
}

稍后,在其他地方,你有这段代码:

Employee some = new EmployeeManager().GetByName("Matias");
some.Roles.Add("Principal");

现在,除了域模型之外,还有一个称为 DomainValidationAspect 的方面,它会在新员工将被持久化到 Repository 实现之前对其进行验证。

整个ValidateEmployeeAspect 可以在添加或更新Employee 时验证它,这是添加角色的情况。它负责加载Employee 的域规则并对其进行验证。域规则使用规范模式实现。

最后,一切都发生在域事务中。

是的,从面向对象编程的角度来看,它似乎是一个贫乏的领域模型,但是面向方面编程呢?

所以问题是……

...领域驱动的设计可能只是因为

  • ...在这种情况下,Employee 角色是使用集合接口添加的?
  • ...从纯面向对象编程的角度来看,Employee 中没有行为?

一些话

我的观点是,面向方面的编程确实有一个缺点,即在领域的主要流程(或任何其他层)中隐藏了很多细节,但对面向对象背后的概念有一个鸟瞰加面向方面的方法将映射到一个丰富的域模型,而不是一个贫乏的域模型

【问题讨论】:

  • Aspects 可能是记录日志或错误处理的好方法,但您确定它们是实现实体验证的最有效方式吗?此外,在富域模型中,实体比验证要多得多……您可能需要的特定域方法呢?你也会把它们放在一个方面吗?
  • @guillaume31 不,我不会在某个方面实现域方法,而是在域管理器类中实现。
  • 对于包含持久性逻辑加上每个特定于其实体的域方法的管理器,我不知道你的域模型是否贫血,但它肯定会受到 的影响附属物肥大 :)
  • 据我了解,贫血域被认为是一种反模式,因为它以没有充分利用 OOP 提供的优势的方式组织代码。请参阅 martinfowler.com/bliki/AnemicDomainModel.html。只要您知道您的方法的缺点并相信它们是可以接受的,那么您应该做适合您的事情。归根结底,这一切都取决于您是否以对您来说物有所值的成本实现了您想要的收益。
  • @AaronHawkins 我知道整篇文章。顺便说一句,我倾向于认为有很多方法可以充分利用 OOP,其中一种可能是我的方法,它利用 AOP 来处理一些可以直接在域对象中实现的东西。好吧,这真的可以以很长的讨论结束……我知道。

标签: c# oop domain-driven-design domain-model aop


【解决方案1】:

想到几点(排名不分先后):

  • 在域驱动设计中,聚合(此处为 Employee绝不允许处于无效状态。它自己强制执行其不变量,因此不需要外部验证。

  • 对我来说,这听起来很像 CRUD。为什么要在简单的 CRUD 上强制 DDD

  • EmployeeManager 的作用除了只是包装存储库方法,因此只是又一层复杂性。

  • repositoryOfEmployeesInstance 对我来说听起来很不自然。就像你不会调用一个类EmployeeClassEmployeeManagerClass,你为什么要在一个对象后缀Instance

    为什么还要附加模式名称(这里是 Repository)?直接打电话给employees。听起来很真实:

    employees.GetByName(name)

    甚至更好: employees.Called(name)

【讨论】:

  • 感谢您的回答。我添加到变量中的前缀或后缀仅用于解释。显然,真实世界的代码看起来就像你想象的那样。
  • 好的,关于 [...] 永远不允许出现在无效的 [...] 中。但我试图证明这可以使用 AOP 甚至按合同设计的方法来获得。
  • 关于为什么在简单的 CRUD 上强制 DDD,这就是你对我的示例代码的结论。我只是想稍微谈谈我的要求。我知道 DDD 不是 CRUD。存储库模式负责将域转换为底层存储可以理解和可以存储的内容。
  • @MatíasFidemraizer 关于您的第二条评论:也许,我从未尝试过。我不确定 AOP 是否适合按照 DDD 的方式创建域模型。当涉及到应用程序的其他部分(如 CRUDy BC、UI、应用程序服务等)时,AOP 可能非常有用。但是对于基于 DDD 的域模型,我宁愿使聚合的不变实施和行为成为其中的显式部分.
  • 就是这样。就我而言,我在许多项目中都使用了这种方法,并且效果很好。你得到了AOP最大的好处:你把精力集中在整个业务的编写上,代码更加清晰,业务验证和其他事情都以声明方式或配置方式绑定到领域对象上。它使很多事情变得更简单,但众所周知的缺点是隐藏了许多抽象技能较低的人难以理解的细节。
【解决方案2】:

我认为面向方面的编程确实具有 在域的主要流程中隐藏大量细节的缺点(或 任何其他层)

IMO,这是在 DDD 中使用此类 AOP 方法的核心问题 - 它隐藏了细节。现在,对于严格的技术领域,例如日志记录,这通常是可取的。 AOP 适用于此类领域,因为从某种意义上说,它是技术领域本身的一个特征——传统 OOP 组合的扩展。另一方面,DDD 针对非技术领域——业务用例。因此,DDD 的目标之一是distillation of domain knowledge 使其尽可能地摆脱技术问题。实现 OOP 的一个步骤是将数据和行为聚集在对象内部。另一个步骤是从行为的技术名称转向行为的更多业务特定名称。这使您可以捕获与行为相关的周围业务上下文,而不仅仅是技术上下文。

对 DDD 有帮助的是一组新的抽象。不是不必要的复杂层,而是创造新语义的东西。正如Dijkstra 所说,抽象应该创建新的语义级别。这可以以 DSL 的形式出现,允许表达与技术问题无关的领域知识。然后,应用程序会将这个 DSL 表示的域附加到基础架构——持久性、UI、服务等。创建这样既富有表现力又易于“附加”的 DSL 是一个巨大的挑战。

要回答您的问题,是的,您的对象模型本身就是贫乏的,即使它通过方面由更丰富的行为组成。但是,这仅针对您的对象模型,对象模型是否贫血只是难题的一部分 - 对象模型是一种战术模式,而不是战略模式。

您的目标似乎朝着正确的方向前进 - 您希望提升抽象级别,使其超越 OOP 单独提供的设施。我只是认为 AOP 的弊端大于 DDD 案例中的好处。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-26
    • 2010-12-20
    • 1970-01-01
    • 2020-01-29
    • 2012-02-04
    • 2017-10-29
    • 2014-01-05
    • 2011-08-17
    相关资源
    最近更新 更多