1

首先让我描述一个点亮的位域。我们有一个网站,客户可以下订单。为了下订单,客户必须提供一些数据。这个过程分为几个步骤。在每一步中,客户端只提供部分数据。当客户完成最后一步时 - 订单所需的所有数据已准备就绪。DDD:实体在技术上,但看起来像价值对象?

所以我们有一个实体StepsProgression。里面有一个价值对象数组“步骤”。他们不存储任何东西,所以它们很简单,非常适合作为价值对象。但为了坚持所有步骤中的用户数据,在StepsProgression也有一个对象StepsData

然后麻烦来了。 StepsData将有setter,来设置用户数据。所以它必须是一个实体。但从领域的角度来看,它不是一个实体。这是一个价值对象,因为我不关心它的身份。我可以用相同的数据替换它,它是可以的。

你在这种情况下能推荐什么?

编辑1

关于域名再次

我们确实有一个订票系统。我们询问了领域专家,我们确实有不同的步骤(或阶段)来填写一些特定的数据,以便为用户预订订购商品。所以Step's和StepsProgressions的概念是可以的。它不与UI结合。例如,在UI方面,我们同时填充两个步骤的数据。

回答

1

一个值对象可以有getter和setter。看起来像你的情况StepsData描述实体的(StepsProgression)状态,使其成为值对象候选者。您可以在值对象本身中拥有值对象属性。价值对象是自包含的,使得从根本上更容易处理。对于DDD纯粹主义者来说,价值对象是不变的,无副作用并且易于测试。

+1

请注意setter:VO是不可变的,因此setter必须返回一个新对象 –

+0

谢谢! StepsData确实是类似于状态的东西,因为当我们向它灌注某些东西时,我们正在改变状态。所以看起来我可以让StepsData成为一个带setter的值对象,它会返回一个新的值对象,我将用它来替换旧的对象? – Clickbeetle

1

通过阅读您的问题/描述,从我的罕见知识看来,我认为您正在建立某种网上商店,预订系统或类似的东西。

这个假设请仔细分析你的域名。问问你自己的问题,什么你的域名是真的,如果StepsProgressionStepStepData真的是“域关注”这样一个排序系统......?

就我个人而言,我觉得这些只是UI工作流程的抽象,并不反映任何领域特定的概念 - 纯粹的应用技术视角。

在这种情况下,它们既不是实体也不是价值对象,因为它们甚至不是域模型的一部分。

我建议要回白板和第一开始建模领域模型只包含域的特定对象(+等等),而无需用户界面或使用记案件太多:

- Order (Entity) 
- OrderNumber (Value Object) 
- Customer (Entity) 
- PaymentType (Entity) 
- OrderTotal (Value Object) 
- … 

通过将它们组合到合适的聚集(事务边界),将它们与存储库一起保存并使用域服务处理它们,您应该能够创建一个“丰富”的域模型。

应用程序服务将使用您的现有域模型来编排您的应用程序的用例(即从用户收集并保留相应数据的正确数据块)。

后来一些规模较小的重构,以领域模型可能需要但请记住,用户界面,应用程序或基础设施的具体问题应遵循的域模型,而不是“泄漏”到它。

也许我的问题完全错了:在这种情况下,造成不便。但是,正如我所看到的那样,对整个域名和相关模式的一般性反思/质疑似乎很有帮助。

+0

感谢您的回复!我们确实在谈论订票系统。预订数据填写过程分为多个步骤,是我们的核心领域。这是我们比竞争对手的优势。步骤模型与UI非常分离(因为在UI中许多步骤都统一并位于一个页面中)。所以你的建议和一般建议一样好,但我们已经有单独的Order模型和单独的Steps模型。 – Clickbeetle

0

首重检查StepsProgression域:如果我们问我们的进程步骤的领域专家,我认为我们得到了一个可以接受的答案。然后我们可以说这是我们域的一部分。

接下来,你说的是这个实体还是VO?是的,它看起来像价值对象,实际上是一个价值对象,因为你不关心整个数据集的身份,它没有独特的含义(如果我不明白domaing是错误的)。

假设你有一个像的操作UI的表单向导和每一步的投入是不同的,我想实现这样的:

public class StepsProgression 
{ 
    private readonly string _userId; 
    public Step1 step1 { get; set; } 
    public Step2 step2 { get; set; } 
    public Step3 step3 { get; set; } 

    public StepsProgression(string userId) 
    { 
     _userId = userId; 
    } 
} 
// Immutable Step Object 
public class Step1 
{ 
    public string input1 { get; } 
    public string input2 { get; } 
    public string input3 { get; } 
} 

您实例StepsProgression中的第一步,所以我假定你只知道他们的步骤是。所以构造函数只设置用户ID。然后,当用户填写第一步输入和点击下一页然后创建一个新的(immutable)的步骤对象,并将其设置为第一步。如果用户点击,从第一步改变任何东西,然后再次单击接下来,然后你再创建一个新的第一步对象并将其分配给StepsProgression。

你能想到的更通用的实现(步骤),但由于输入是步骤之间的所有不同,这种明确的方式为您提供了编码约定。

执行编辑: 据我所知,StepsProgressions包含动态一套你刚刚创建StepsProgression之前确定的步骤。然后,假设你知道stepTypes事先我会改变我的实现如下:

public class StepsProgression 
{ 
    private readonly string _userId; 
    private readonly IStepProgressionBuilder _builder = new StepProgressionBuilder(); 
    public IEnumerable<IStep> Steps { get; set; } 

    public StepsProgression(string userId, IEnumerable<string> stepTypes) 
    { 
     _userId = userId; 
     foreach (var step in stepTypes) // foreach smells here but you got the point 
     { 
      _builder.AddConcreteStep(step) // step = "Step1" for instance. 
     } 
     Steps = _builder.Build(); 
    } 
} 
// Immutable Step Object 
public class Step1 : IStep 
{ 
    public string input1 { get; } 
    public string input2 { get; } 
    public string input3 { get; } 
} 
// Builder Pattern with Fluent Methods 
public interface IStepProgressionBuilder 
{ 
    // stepType = "step1" for instance. You have concrete Step1 class, so you return it. 
    // Start with a switch, then you refactor. (May involve some reflection) 
    void AddConcreteStep(string stepType); 
    IEnumerable<IStep> Build(); 
} 

同样,你可以使用一个通用的步骤类,但随后你的建设者应该shomehow建立通过采取这一步骤应包含每个数据的步骤。如果你的步骤可能有超过3个属性,这将变成一个代码。这就是为什么我更喜欢有具体的步骤类,以便我可以将其构建为一个完整的数据集。

+0

谢谢你的回复。我们问领域专家和步骤是好的,但步骤不耦合到用户界面。我喜欢你的例子,但是我想在创建StepsProgression的时候已经有了Steps的实例。因为步骤级数可以有很多不同的步骤,所以一个StepsProgression可以完全不同于另一个。我需要一种方法来访问一个StepsProgression中所有步骤的所有数据,这就是为什么需要StepsData。 – Clickbeetle

+0

据我所知,您的StepsProgression包含一个“动态”步骤集,可能因表单向导的不同而不同,您希望使用通用的StepsProgression类。如果我没有错,我按照您的需求编辑了解决方案。 – stratovarius

0

因此最后我选择将StepsData作为Value Object。它给了我在StepsProgression以外的写保护的好处。

我可以通过StepsProgression以外的StepsData只读,不用担心,因为如果外面的某个人在StepsData上调用setter,则会返回一个新对象。在StepsProgression之外,您不能重写原始StepsData。所以即使有人打电话给setter,StepsProgression中的原始StepsData仍然不变。