【问题标题】:How to handle Domain Driven Design when domain is dynamic / changes当域是动态/变化时如何处理域驱动设计
【发布时间】:2014-05-28 08:32:03
【问题描述】:

为了清楚我的意思,让我们建立一个可能需要这样做的假设场景。

假设我正在为一家管理苏格兰威士忌酿酒厂的公司开发一些软件。每个啤酒厂公司都可以拥有该软件的副本来管理所有业务物流。

假设我有一个Kettle 对象,在它上面我们会有一些属性,例如.Colour.Material

但是,如果 Glenfiddich 啤酒厂想要在该域上拥有另一处房产怎么办?例如.Size 和其他一些啤酒厂并不关心以上所有内容,只对.Content.DateFermentationStarted 感兴趣?

在 OOP 中,有没有一种方法可以使我的 Kettle 对象动态化(具有不同的属性和方法),具体取决于名为 Customer 的一些参数?

如果我没记错的话,这和 Polymorphsim 很不一样。

【问题讨论】:

  • Google 用于多租户或多租户应用程序(只有特定租户内的用户才能使用该租户内的对象)。还请看 Vaughn Vernon 的书 Implementing Domain-Driven Design
  • 感谢 MaxS。谷歌返回的很多东西似乎暗示我“为每个客户创建不同的应用程序”..真的没有更好的方法吗? ://
  • 多租户应用程序允许您在同一应用程序中定位多种类型的啤酒厂。似乎谷歌没有返回正确的结果;-)
  • 您可能遗漏了一个概念,它是代表 Kettle 定义的抽象概念(例如,它具有哪些属性)。现在,这无数的动态属性引出了一个问题,即行为在哪里以及为什么/如何受到这些动态属性的影响。 “为客户打造”是一种处于不成熟领域或您知之甚少的领域的味道。无论如何,如果您还想在运营端扩展它(更多部署),您将需要挖掘仍然为您的客户提供价值的通用抽象。

标签: oop domain-driven-design


【解决方案1】:

在这种情况下,您不能应用太多 DDD,因为域概念定义从一个客户端更改为另一个客户端。我的意思是我们都有一个水壶,但可以有客户想要的任何财产。你如何应用行为?更确切地说是什么?

您的“域”对象最多是数据结构。谁在定义对象时有发言权以及自定义选项是什么,这确实很重要。例如,如果客户可以从预定义的属性中进行选择,那么您就有了一些回旋余地。

但是,如果他们可以添加任何名称的任何属性,那么您将失去语义,最终只能得到由魔术字符串组成的数据结构。而且您无法强制执行业务规则,除非您还拥有一个客户可以用来定义规则的业务规则编辑器。这对于开发人员和客户来说都是相当复杂的。

因此,IMO,DDD 并不真正适合此,除非您为每个客户构建单独的应用程序,但正确的 OOP 仍然是。不过,您将使用数据结构,并且 ORM 可能会在这里有所帮助。

【讨论】:

  • +1 for “您的“域”对象最多是数据结构。”。不可能对这个域进行完全建模。接受这个事实,让这种无模式的本质成为您设计的内在组成部分。
  • 我必须同意这里。 @Ciwan,如果这些属性纯粹是信息性的,那么您可以轻松地建模。但是,一旦您要求这些不同的属性参与行为,您就会遇到问题。您最终会得到每个客户完全不同的模型(物理代码库)或一些通用结构,最终从每个客户的逻辑角度组成一个独特的结构。你必须在这里选择你的毒药:)
  • 谢谢大家。 @EbenRoux 这些属性纯粹是信息性的,如果客户 A 在未来某个日期希望检索有关水壶的信息,它可以取回它最初存储的所有信息。所以基本上 Kettle 输入表单对于不同的客户会有不同的字段。我该怎么办?这就是我觉得困难的地方。
【解决方案2】:

如果这些属性是动态的,您有多种选择。

  1. 拥有一个具有通用属性的基类,然后派生具有已定义新属性的新类。 然而,这个解决方案有一个问题——您必须为每个需要特殊属性的客户创建不同的构建。 优点是系统在代码级别的显式属性意义上是完整记录的。

  2. 属性建模为单独的自治类,并能够创建属性实例并将它们分配给运行时的域对象。 属性可以在 DB 中定义(使用它们的名称,可能是数据类型),然后分配给某些需要的域类。这样就不需要为每个不同的配置进行新的构建,只需要更新数据库。

这个简单的类图显示了这个想法:

附加属性在数据库中定义,而不是在运行时读取并分配给域对象。

【讨论】:

    【解决方案3】:

    听起来您只需要用户定义的自定义属性:

    public class Kettle
    {
        public Dictionary<string, string> CustomProperties;
    }
    
    
    var kettle = new Kettle();
    kettle.CustomProperties["Size"] = "1";
    kettle.CustomProperties["Content"] = "Some Content";
    kettle.CustomProperties["DateFermentationStarted"] = "1/1/2014";
    

    您可以将这些属性作为单个客户的键/值对保存在数据库中。然后创建配置 GUI 或 XML 配置文件以定义每个安装的自定义属性。当您在系统中创建 Kettle 对象时,您还将从您选择的存储库中加载自定义属性。

    这里的挑战是客户是否需要附加到这些属性的业务逻辑。由于属性是动态的,因此业务逻辑也需要是动态的(即插件、脚本等)。这涉及到一系列完全不同的问题。

    【讨论】:

      【解决方案4】:

      首先感谢你让我为你的英国人的例子发笑:-D

      我会做的,记住我是一个初学者,是有一个处理所有属性的域对象,以及一个派生类型,它还提供一个接口来告诉租户允许读取哪些属性。

      interface iKettle
      {
          public getColour() : ?Colour;
      }
      
      interface iUserKettle extends iKettle
      {
          public canReadColour() : bool;
      }
      
      class Kettle implements iKettle
      {
          public getColour() : ?Colour
          {
              return this.colour;
          }
      }
      
      class UserKettle extends Kettle implements iUserKettle
      {
          public UserKettle(....., UserKettleProperties props)
          {
              this.props = props;
          }
      
          public canReadColour() : bool
          {
              return this.props.canRead("colour");
          }
      
          public getColour() : ?Colour // override
          {
              if(this.canReadColour())
                  return super.getColour();
              else
                  throw new Exception("You aren't allowed to know the colour");
          }
      }
      

      canRead* 方法的要点是有一个明确的方法来知道一个属性是否可读。

      假设您有一个 Kettle 字段,它返回一个值并且也可以为空。 然后你做 instance.getSomeProperty() 你得到 NULL。但是您得到 NULL 是因为该值实际上是 NULL 还是因为您不允许读取它?这就是你有第二种方法 canReadSomeProperty() 的原因,它告诉你是否允许调用 getter 方法。

      或者使用 canRead* 方法并抛出异常,您可能会考虑只返回 NULL 或空对象。

      【讨论】:

      • 我很高兴您发现示例场景很有趣 :D 您的建议假设我事先完全知道所有客户的所有属性。情况并非如此,在管理面板(我计划拥有)上,系统管理员想说对于客户 Famous Grouse,我需要 Kettle 对象上的 Location 属性
      • 仍然存在“域”,它就是 Kettle。我正在编辑我的答案
      • 但首先要问一个问题:为什么要限制某些客户的属性?你不能返回NULL吗?
      猜你喜欢
      • 1970-01-01
      • 2012-07-18
      • 2015-03-30
      • 1970-01-01
      • 1970-01-01
      • 2011-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多