【问题标题】:How do I determine the ID of an aggregate root added to a repository?如何确定添加到存储库的聚合根的 ID?
【发布时间】:2011-06-08 09:21:19
【问题描述】:

假设我有一个通用的存储库接口,如下所示:

public interface IRepository<T>
{
    Add(T item);
    Delete(int itemId);
    Update(T item);
}

通常,通过 IRepository.Add() 添加的项目的新 ID 将由某个后端数据库确定,但仅在提交整个事务/工作单元后才确定。所以我相当肯定 IRepository.Add() 返回添加项目的新 ID 是错误的。存储库真的不应该知道 ID 是如何创建的。这是正确的吗?

如果是这种情况,我们还能如何确定添加到存储库中的项目的新 ID,或者我什至应该这样做?我知道像 NHibernate 这样的 ORM 能够自动将内存中的对象替换为具有正确 ID 的新对象,但我正在尝试设计我的存储库而没有考虑任何特定的 ORM 实现。

例如,假设我有一个客户可以在其中下订单的网站。一位新客户选择结帐并被发送到一个表格以填写他们的详细信息。此信息用于创建存储在 CustomerRepository 中的 Customer 对象。现在需要创建他们的订单信息,但订单需要通过他们的 ID 引用客户?

Customer newCustomer = new Customer(first, last, address, phone dateOfBirth);
customerRepository.Add(newCustomer);

//How would I determine customerId??
Order newOrder = new Order(customerId, shippingAddress, billingAddress);
newOrder.AddOrderItem("widget");
newOrder.AddOrderItem("doohicky");
newOrder.AddOrderItem("stuff");

【问题讨论】:

    标签: asp.net-mvc domain-driven-design ddd-repositories


    【解决方案1】:

    在您给出的示例中,我将一步创建CustomerOrder,并将域对象传递给域对象,而不是传递ID:

    Customer newCustomer = new Customer(first, last, address, phone dateOfBirth);
    
    // Pass the customer rather than the CustomerId:
    Order newOrder = new Order(newCustomer , shippingAddress, billingAddress);
    newOrder.AddOrderItem("widget");
    newOrder.AddOrderItem("doohicky");
    newOrder.AddOrderItem("stuff");
    
    customerRepository.Add(newCustomer);
    orderRepository.Add(newOrder);
    
    // SaveChanges()
    

    ...保存更改后,框架会自动填充CustomerOrder的Id,并通过Customer填写Customer.IdOrder.customerId等对象已分配给Order

    【讨论】:

      【解决方案2】:

      我在客户端上生成 id (a la CombGuid.NewGuid()),然后将其传递给构造函数。使用数据库身份时的方法has serious disadvantages

      【讨论】:

        【解决方案3】:

        埃里克,

        在您提到的场景中,我没有看到任何 CommitChanges() 正在进行。我会将所有内容包装在事务范围中,然后在添加订单行之前点击 customerRepository.CommitChanges()。然后,您应该能够从新创建的客户对象中获取 id 并按如下方式使用它:

        Order newOrder = new Order(newCustomer.Id, shippingAddress, billingAddress);
        

        然后,如果订单失败,您可以回滚所有内容并通过不点击 scope.Complete() 来保持其原子性。

        希望这会有所帮助..

        【讨论】:

        • 在我看到的示例中,存储库不是您所描述的提交更改的地方。相反,每个存储库都引用一个可以提交或回滚的 UnitOfWork 对象。但是,我的印象是 UnitOfWork 对象是典型的设置,因此它只能回滚尚未提交的更改。
        【解决方案4】:

        无论您是否使用 NHibernate,我都觉得它采用的方法是正确的。您对任何域对象的目标是在任何时候只在内存中拥有该对象的一个​​实例,即您永远不应该有两个对象代表相同的数据库记录。因此,如果数据库使用新 id 更新了记录,内存中的域对象也应该使用该 ID 更新,因为这是该记录的“一个”真实表示。

        在调用Add 后,对象的 ID 已设置,然后您可以对该对象进行进一步更改并调用Update,而无需过多了解您的实现。

        【讨论】:

        • 所以应该假设IRepository.Add()的任何具体实现都会设置被添加项目的ID?
        【解决方案5】:

        根据 DDD,您的界面比存储库更像 DAO:

        http://codebetter.com/iancooper/2011/04/12/repository-saveupdate-is-a-smell/

        就像 Steve Wilkes 提到的那样,您应该保持对客户的引用,而不是客户 ID,因此当处理工作单元时,它将在持久性存储(SQL DB、Web 服务等)中创建正确的链接那些实体

        在此处了解有关 DAO 的更多信息:http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html

        【讨论】:

          【解决方案6】:

          无论数据库ID,域实体都应该有自己的ID策略,所以最好在域层生成你的id,或者如果你真的需要在数据库中生成id,那么在自动生成的数据库旁边添加另一个域层生成的域标识符身份证。

          在应用存储库模式的域驱动设计中,不应将域与数据库绑定,因此依赖数据库创建 id 不是一个好主意。

          另一点是,您可能希望关联客户,以便不仅仅是放置客户 ID,这使得域层丰富而可靠。

          【讨论】:

            猜你喜欢
            • 2010-11-16
            • 1970-01-01
            • 1970-01-01
            • 2014-06-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-07-06
            相关资源
            最近更新 更多