【问题标题】:Scaling a rich domain model扩展富域模型
【发布时间】:2010-09-27 13:32:30
【问题描述】:

领域驱动设计鼓励您使用丰富的领域模型。这意味着所有的领域逻辑都位于领域模型中,并且领域模型是至高无上的。持久性成为一个外部问题,因为理想情况下域模型本身对持久性一无所知(例如数据库)。

我一直在一个中等规模的单人项目(> 100k 行 Java)中使用它,我发现了许多优点,主要是它提供的灵活性和可重构性优于面向数据库的方法。我可以添加和删除域类,点击几个按钮,一个全新的数据库模式和 SQL 层就会推出。

但是,我经常遇到这样的问题,即我发现很难将丰富的域逻辑与支持应用程序的 SQL 数据库这一事实相协调。通常,这会导致典型的“1+N 查询问题”,即您获取 N 个对象,然后对再次触发查询的每个对象执行一个重要的方法。手动优化此过程允许您在恒定数量的 SQL 查询中完成该过程。

在我的设计中,我允许系统插入这些优化版本。我通过将代码移动到包含数十个特定于域的查询(例如 getActiveUsers)的“查询模块”中来做到这一点,其中两个我都有-memory(简单且不可扩展)和基于 SQL(用于部署)的实现。这让我可以优化热点,但有两个主要缺点:

  • 我正在有效地将我的一些域逻辑移动到它并不真正属于的地方,实际上甚至将其推入 SQL 语句中。
  • 该过程要求我仔细阅读查询日志以找出热点在哪里,然后我必须重构代码,通过将其降低为查询来降低其级别抽象。

有没有更好、更简洁的方法来协调域驱动设计及其富域模型与您不能将所有实体都保存在内存中并因此仅限于数据库后端这一事实?

【问题讨论】:

    标签: model scalability domain-driven-design


    【解决方案1】:

    至少有两种方法来看待这个问题,一种是技术“我可以做些什么来更智能地加载我的数据”版本。我所知道的唯一真正聪明的事情是动态集合,这些集合部分加载,其余部分按需加载,可能预加载部分。 JavaZone 2008 about this有一个有趣的谈话

    在我使用 DDD 时,我更关注第二种方法;如何在不牺牲太多 DDD 优点的情况下制作我的模型,使其更“可加载”。多年来我的假设一直是,许多 DDD 模型对领域概念进行建模,这些领域概念实际上是所有允许的领域状态的总和,跨越所有业务流程以及随着时间的推移在每个业务流程中出现的不同状态。我相信,如果域模型在进程/状态方面稍微标准化一点,很多这些加载问题就会大大减少。这通常意味着没有“订单”对象,因为订单通常存在于多个不同的状态,这些状态具有相当不同的语义(ShoppingCartOrder、ShippedOrder、InvoicedOrder、HistoricalOrder)。如果您尝试将其封装为单个 Order 对象,您总是会遇到很多加载/构造问题。

    但这里没有灵丹妙药..

    【讨论】:

    • 您描述的问题主要与继承、代理和惰性状态问题有关(至少在我的上下文中),我已经为我的项目“解决”了这些问题。感谢您的建议,但我真的不明白改变我的模型的表达方式会对我的问题产生什么影响。
    • 当然,除非我计算的数据更少,而更多的数据显式存储在字段中,但这确实是一种非规范化形式。
    • 我认为这是有效的;我还没有发现建模技术是普遍有效的。很多时候,您可以在域模型的多种方法中进行选择,但并非总是如此。
    【解决方案2】:

    根据我的经验,这是做事的唯一方法。如果您编写的系统试图完全隐藏或抽象持久层,那么您将无法使用持久层的细节来优化事物。

    我最近一直在遇到这个问题,并且一直在研究一种解决方案,在该解决方案中,持久层可以选择实现代表优化的接口。我一直在玩它,但要使用您的 ListAUsers 示例,它会像这样......

    首先编写一个 ListAllUsers 方法,该方法在域级别执行所有操作。有一段时间这会起作用,然后它会开始变得太慢。

    当使用富域模型变得缓慢时,创建一个名为“IListActiveUsers”的接口(或者可能更好)。并让您的持久性代码使用适当的技术(可能是优化的 SQL)来实现此接口。

    现在您可以编写一个层来检查这些接口并调用特定方法(如果存在)。

    这并不完美,我对这类事情没有太多经验。但在我看来,关键是要确保如果您使用的是完全幼稚的持久性方法,那么所有代码​​都应该仍然有效。任何优化都需要作为补充来完成。

    【讨论】:

    • 您所描述的几乎就是我的做法。除了我的查询是抽象 QueryModule 类中的方法,而不是单独的接口/实现对,但这并不是主要的区别。至少我现在感觉不那么孤独了:-)
    • Udi Dahan 在以下演示文稿中谈到了这项技术:infoq.com/presentations/Making-Roles-Explicit-Udi-Dahan
    【解决方案3】:

    不,不是真的。反正我不知道(尽管我很想听听任何 DDD 支持者的相反回应)。

    根据我自己的经验,以及与我一起工作的经验丰富的团队的经验,如果您希望从数据库支持的应用程序中获得最佳性能,那么将其架构转变为面向服务的架构是不可避免的。我写了more about this here(这篇文章讨论了延迟加载的属性,但您可以考虑将这一点应用于需要检索更多数据以完成其工作的类上的任何方法)。

    正如您现在所做的那样,您可以从一个丰富的域模型开始,并在必要时出于性能原因将其转换为面向服务的。只要您定义了绩效目标并且正在实现它们,就没有必要改变一切。我认为这是一种相当不错的务实方法。

    【讨论】:

    • 我想一个真正的基本改进方法需要不同的语言范式,您可以在其中以声明方式描述部分逻辑,以便它可以自动编译为查询......但这种故事有其自己的问题的份额:-)
    【解决方案4】:

    我相信您应该考虑将查询层作为您的域逻辑的一部分。您应该允许自己编写优化查询,这些查询只能通过对持久性解决方案的“深入”了解来完成。不要试图抽象出一切。 此外,批处理是您的应用程序的另一部分,也应该允许您了解您的领域。我发现没有必要仅仅因为我无法将批处理放入我的域模型中而尝试避免批处理。但是,您可以结合这些方法:使用查询找出需要更改的对象,然后使用您的域逻辑将它们的 id 排队并自行处理。

    【讨论】:

      猜你喜欢
      • 2012-12-31
      • 2014-06-12
      • 1970-01-01
      • 2012-11-17
      • 1970-01-01
      • 2010-11-22
      • 1970-01-01
      • 2014-01-05
      • 1970-01-01
      相关资源
      最近更新 更多