领域驱动设计:如何处理数据字段多的复杂模型?
Domain driven design: How to deal with complex models with a lot of data fields?
好吧,我正在尝试为我的应用程序应用域驱动设计原则,使用包含数据字段和业务逻辑的丰富域模型。我读过很多 DDD 书籍,但似乎他们的领域模型(称为实体)非常简单。当我有一个包含 10-15 个数据字段的域模型时,这就成了一个问题,例如下面的一个:
class Job extends DomainModel{
protected int id;
protected User employer;
protected string position;
protected string industry;
protected string requirements;
protected string responsibilities;
protected string benefits;
protected int vacancy;
protected Money salary;
protected DateTime datePosted;
protected DateTime dateStarting;
protected Interval duration;
protected String status;
protected float rating;
//business logic below
}
如你所见,这个领域模型包含了很多数据字段,而且都很重要,不能剥离。我知道一个好的富域模型不应该包含 setter 方法,而是将其数据传递给构造函数,并使用业务逻辑改变状态。但是,对于上面的域模型,我不能将所有内容都传递给构造函数,因为这会导致构造函数方法中的参数超过 15 个。一个方法不应该包含超过 6-7 个参数,你不觉得吗?
那么我该如何处理具有大量数据字段的领域模型呢?我应该尝试分解它吗?如果是这样,如何?或者,我应该只使用 Builder class 或反射在实例化时初始化其属性,这样我就不会用这么多参数污染构造函数?谁能给点建议?谢谢。
您错过了值对象的概念。值对象是小的、不可变的对象,在各自的域中具有意义。
我不知道您的域的具体情况,但查看您的 Job
实体,可能有一个值对象 JobDescription
如下所示:
class JobDescription {
public JobDescription(string position, string requirements, string responsibilities) {
Position = position;
Requirements = requirements;
Responsibilities = responsibilities;
}
public string Position {get;}
public string Requirements {get;}
public string Responsibilities {get;}
}
这是 C# 代码,但我认为无论您使用何种语言,思路都应该清晰。
基本思想是以在各自域中有意义的方式对值进行分组。这当然意味着值对象也可以包含其他值对象。
您还应该确保值对象是按值而不是按引用进行比较的,例如通过在 C# 中实现 IEquatable<T>
。
如果您使用这种方法重构您的代码,您的实体上将获得更少的字段,因此使用构造函数注入(强烈推荐)再次变得可行。
关于您的示例代码但与问题没有直接关系的进一步说明:
领域模型是全部,实体是其中的一部分。所以你的基地 class 应该被称为 Entity
而不是 DomainModel
.
您应该创建 class private
的字段,并在需要维护封装的地方提供 protected
访问器。
您的工作域模型对象中发生了很多事情 - 它似乎混合了大量的关注点,并且(至少对我而言)暗示了许多有界上下文,其中一些很容易辨别为了举例说明。
- 薪酬(工资、福利)
- 组织职位(报告线)
- 人员规格(技能)
- 工作规范(职责)
- 等等
当您考虑与您的 'Job' 模型交互的事物时,是否有任何事物需要检查或改变工资 属性 和行业 属性,例如?
在不了解域的全部细微差别的情况下,您担任职位所获得的薪水与您从事的行业并没有真正联系,是吗?不是修辞点;这些是您需要询问领域专家的问题。
如果他们没有任何交互,那么你已经确定这两个东西存在于两个不同的边界上下文中。 Salary 端不需要与 Industry 端进行任何交互,反之亦然,即使它们交互了,它们是否需要同时在同一进程中保持状态?
想想一个人如何成为一名员工的生命周期;一个人申请一份工作。工作有规范,薪资范围。该人参加面试。雇用者向该人提供职位。该人接受。这个人现在是雇员,不再是候选人。新员工现在正在累积假期和福利,并有开始日期等。
DDD 告诉我们,单一、统一的世界观很少能正确地解决任何问题。请探索 BOUNDED CONTEXTS - 您的软件将因此变得更加柔韧和灵活。
好吧,我正在尝试为我的应用程序应用域驱动设计原则,使用包含数据字段和业务逻辑的丰富域模型。我读过很多 DDD 书籍,但似乎他们的领域模型(称为实体)非常简单。当我有一个包含 10-15 个数据字段的域模型时,这就成了一个问题,例如下面的一个:
class Job extends DomainModel{
protected int id;
protected User employer;
protected string position;
protected string industry;
protected string requirements;
protected string responsibilities;
protected string benefits;
protected int vacancy;
protected Money salary;
protected DateTime datePosted;
protected DateTime dateStarting;
protected Interval duration;
protected String status;
protected float rating;
//business logic below
}
如你所见,这个领域模型包含了很多数据字段,而且都很重要,不能剥离。我知道一个好的富域模型不应该包含 setter 方法,而是将其数据传递给构造函数,并使用业务逻辑改变状态。但是,对于上面的域模型,我不能将所有内容都传递给构造函数,因为这会导致构造函数方法中的参数超过 15 个。一个方法不应该包含超过 6-7 个参数,你不觉得吗?
那么我该如何处理具有大量数据字段的领域模型呢?我应该尝试分解它吗?如果是这样,如何?或者,我应该只使用 Builder class 或反射在实例化时初始化其属性,这样我就不会用这么多参数污染构造函数?谁能给点建议?谢谢。
您错过了值对象的概念。值对象是小的、不可变的对象,在各自的域中具有意义。
我不知道您的域的具体情况,但查看您的 Job
实体,可能有一个值对象 JobDescription
如下所示:
class JobDescription {
public JobDescription(string position, string requirements, string responsibilities) {
Position = position;
Requirements = requirements;
Responsibilities = responsibilities;
}
public string Position {get;}
public string Requirements {get;}
public string Responsibilities {get;}
}
这是 C# 代码,但我认为无论您使用何种语言,思路都应该清晰。
基本思想是以在各自域中有意义的方式对值进行分组。这当然意味着值对象也可以包含其他值对象。
您还应该确保值对象是按值而不是按引用进行比较的,例如通过在 C# 中实现 IEquatable<T>
。
如果您使用这种方法重构您的代码,您的实体上将获得更少的字段,因此使用构造函数注入(强烈推荐)再次变得可行。
关于您的示例代码但与问题没有直接关系的进一步说明:
领域模型是全部,实体是其中的一部分。所以你的基地 class 应该被称为
Entity
而不是DomainModel
.您应该创建 class
private
的字段,并在需要维护封装的地方提供protected
访问器。
您的工作域模型对象中发生了很多事情 - 它似乎混合了大量的关注点,并且(至少对我而言)暗示了许多有界上下文,其中一些很容易辨别为了举例说明。
- 薪酬(工资、福利)
- 组织职位(报告线)
- 人员规格(技能)
- 工作规范(职责)
- 等等
当您考虑与您的 'Job' 模型交互的事物时,是否有任何事物需要检查或改变工资 属性 和行业 属性,例如?
在不了解域的全部细微差别的情况下,您担任职位所获得的薪水与您从事的行业并没有真正联系,是吗?不是修辞点;这些是您需要询问领域专家的问题。
如果他们没有任何交互,那么你已经确定这两个东西存在于两个不同的边界上下文中。 Salary 端不需要与 Industry 端进行任何交互,反之亦然,即使它们交互了,它们是否需要同时在同一进程中保持状态?
想想一个人如何成为一名员工的生命周期;一个人申请一份工作。工作有规范,薪资范围。该人参加面试。雇用者向该人提供职位。该人接受。这个人现在是雇员,不再是候选人。新员工现在正在累积假期和福利,并有开始日期等。
DDD 告诉我们,单一、统一的世界观很少能正确地解决任何问题。请探索 BOUNDED CONTEXTS - 您的软件将因此变得更加柔韧和灵活。