代码之家  ›  专栏  ›  技术社区  ›  Ed I

如何表示将聚合根添加到存储库中?

  •  5
  • Ed I  · 技术社区  · 16 年前

    假设我们有一个与客户和订单行相关的订单类型的聚合根实体。当我想到一个订单实体时,更自然地将其概念化为没有ID的定义。没有ID的订单似乎比订单更好地表示为一个订单请求。

    要向存储库添加订单,我通常会看到人们在没有ID的情况下实例化订单,然后让存储库完成对象:

    class OrderRepository
    {
        void Add(Order order)
        {
            // Insert order into db and populate Id of new order
        }
    }
    

    我喜欢这种方法的一点是,您正在向orderrepository添加一个订单实例。这很有道理。但是,订单实例没有ID,并且在存储库的使用者范围内,订单没有ID对我来说仍然没有意义。我可以将orderrequest定义为订单的实例并将其添加到存储库中,但这感觉像是从橙色衍生出一个苹果,然后将其添加到一个橙色列表中。

    或者,我也看到了这种方法:

    class OrderRepository
    {
        Order AddOrder(Customer customer)
            // It might be better to call this CreateOrder
        {
            // Insert record into db and return a new instance of Order
        }
    }
    

    我喜欢这种方法的一点是,没有ID的订单是未定义的。存储库可以在创建和返回订单实例之前创建数据库记录并收集所有必需的字段。这里闻到的是这样一个事实:您从未向存储库中添加订单的实例。

    无论哪种方式都有效,所以我的问题是:我必须接受这两种解释中的一种,还是有一种最佳实践来模拟插入?

    我找到了类似的答案,但对于价值对象: how should i add an object into a collection maintained by aggregate root . 当涉及到一个值对象时,没有混淆,但我的问题涉及到一个具有从外部源(自动生成的数据库ID)派生的标识的实体。

    1 回复  |  直到 14 年前
        1
  •  5
  •   Mark Seemann    16 年前

    我想先排除第二种方法。它不仅看起来有违直觉,而且违反了一些好的设计原则,例如 Command-Query Separation 以及 Principle of Least Surprise .

    其余选项取决于域逻辑。如果域逻辑规定没有ID的顺序是无意义的,那么ID是必需的顺序不变量,我们必须对其进行建模,以便:

    public class Order
    {
        private readonly int id;
    
        public Order(int id)
        {
            // consider a Guard Clause here if you have constraints on the ID
            this.id = id;
        }
    }
    

    注意,通过标记 id 字段AS readonly 我们已经使它成为不变量。对于给定的订单实例,我们无法更改它。这个非常适合 Domain-Driven Design 实体 模式。

    您可以通过在构造函数中放入一个guard子句来进一步加强域逻辑,以防止ID为负或为零。

    到目前为止,您可能想知道这将如何处理数据库中自动生成的ID。嗯,没有。

    没有好的方法来确保提供的ID尚未使用。

    这给你两个选择:

    • 将ID更改为guid。这允许任何调用方为新订单提供唯一的ID。但是,这也要求您使用guid作为数据库键。
    • 更改API,这样创建一个新的订单时就不会使用order对象,而是像您建议的那样使用orderrequest——orderrequest可以与order类几乎相同,减去id。

    在许多情况下,创建新订单是一种业务操作,在任何情况下都需要特定的建模,因此我认为区分这一点没有问题。尽管order和orderrequest在语义上非常相似,但它们甚至不必在类型层次结构中相关。

    我甚至可以说他们 不应该 关联,因为orderrequest是 值对象 鉴于订单是 实体 .

    如果采用这种方法,addorder方法必须返回一个订单实例(或者至少是ID),否则我们就无法知道刚刚创建的订单的ID。这让我们回到了违反CQS这就是为什么我倾向于 实体ID首选guid .