【问题标题】:Domain Driven Design - How to handle updates for parts of your aggregrate roots领域驱动设计 - 如何处理部分聚合根的更新
【发布时间】:2013-10-16 15:23:03
【问题描述】:

背景:我有一个 Person 域对象。它是一个聚合根。我已经包含了以下课程的一部分。

我正在公开执行对象行为的方法。例如,要添加 BankAccount,我有 AddBankAccount() 方法。我没有包含该类的所有方法,但足以说明任何公共属性都必须使用方法进行更新。

我将创建一个 IPerson 存储库来处理 CRUD 操作。

public interface IPersonRepository
{ 
    void Save(Person p);
    //...other methods
}

问题:当我们更新现有人员时,如何告诉存储库哪些字段需要更新?例如,如果我向现有人员添加银行帐户,我如何在调用 repository.Save() 时将此信息传达给存储库?

在存储库中很容易确定何时创建新人,但是当现有人员存在并且您更新该人的字段时,我不确定如何将其传达给存储库。

我不想用更新哪些字段的信息污染我的 Person 对象。

我可以在存储库上使用单独的方法,例如 .UpdateEmail()、AddBankAccount(),但这感觉有点矫枉过正。我想在存储库上使用一个简单的 .Save() 方法,它可以确定需要以某种方式更新的内容。

其他人是如何处理这种情况的?

我已经搜索了网络和 stackoverflow,但没有找到任何东西。我一定没有正确搜索,因为当涉及到 DDD 范式中的持久性时,这似乎很简单。我对 DDD 的理解也可能离题了 :-)

public class Person : DomainObject
{
    public Person(int Id, string FirstName, string LastName,
        string Name, string Email)
    {
        this.Id = Id;
        this.CreditCards = new List<CreditCard>();
        this.BankAccounts = new List<BankAccount>();
        this.PhoneNumbers = new List<PhoneNumber>();
        this.Sponsorships = new List<Sponsorship>();
    }

    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public string Name{ get; private set; }
    public string Email { get; private set; }
    public string LoginName { get; private set; }

    public ICollection<CreditCard> CreditCards { get; private set; }

    public ICollection<BankAccount> BankAccounts { get; private set; }

    public ICollection<PhoneNumber> PhoneNumbers { get; private set; }  

    public void AddBankAccount(BankAccount accountToAdd, IBankAccountValidator bankAccountValidator)
    {
        bankAccountValidator.Validate(accountToAdd);

        this.BankAccounts.Add(accountToAdd);
    }

    public void AddCreditCard(CreditCard creditCardToAdd, ICreditCardValidator ccValidator)
    {
        ccValidator.Validate(creditCardToAdd);

        this.CreditCards.Add(creditCardToAdd);
    }

    public void UpdateEmail(string NewEmail)
    {
        this.Email = NewEmail;
    }

【问题讨论】:

  • 嗨,我看到没有人真正回答你的问题。好吧,至少我不认为说“使用 ORM”是一种解决方案。你最后做了什么?

标签: domain-driven-design repository-pattern ddd-repositories aggregateroot


【解决方案1】:

S#arp Architecture 项目中有一个 Repository 接口示例。它类似于PoEAA Data Mapper,因为它也用于CRUD操作。

public interface IRepositoryWithTypedId<T, IdT>
{
    T Get(IdT id);    
    IList<T> GetAll();    
    IList<T> FindAll(IDictionary<string, object> propertyValuePairs);    
    T FindOne(IDictionary<string, object> propertyValuePairs);    
    T SaveOrUpdate(T entity);    
    void Delete(T entity);    
    IDbContext DbContext { get; }
}

如您所见,实体的特定属性没有更新方法。整个实体作为参数提供给方法SaveOrUpdate

当您的域实体的属性被更新时,您应该告诉您的 Unit of Work 该实体是“脏”的,应该保存到存储中(例如数据库)

您不应该使用有关更新字段的信息污染您的 Person 对象,但如果实体更新,则需要跟踪信息。

DomainObject 类的方法可能会告诉“工作单元”实体是“新”、“脏”还是“已删除”。然后您的 UoW 本身可能会调用适当的存储库方法 - “SaveOrUpdate”或“Delete”。

尽管像 NHibernateEntityFramework 这样的现代 ORM 框架都有自己的“工作单元”实现,但人们倾向于为它们编写自己的包装器/抽象。

【讨论】:

  • 感谢您的 cmets,非常感谢!不幸的是,我仍然不知道我的 person 对象如何使存储库知道已添加信用卡并且需要保存,或者电子邮件已更新并且只需要更新。 My Person 对象包含许多其他对象,但实际上它们映射到多个数据表。
  • 你使用任何 ORM 吗?您使用什么与存储库中的数据库进行交互?通常有两个选项:1st -> 如果 CreditCard 是聚合根的子节点并且没有 Person 就没有意义,那么您应该更新/创建 Person(数据库中的 Persons 表)然后更新集合带有 PersonRepository 的 CreditCards(数据库中的 CreditCards 表)。 2nd -> 如果 CreditCard 是一个没有 Person 的实体,那么您应该使用它自己的 CreditCardRepository 创建/更新 CreditCard,然后使用 PersonRepository 创建/更新 person。
  • 再次感谢您的 cmets。在您的第一个场景中,我难以理解的是 PersonRepsitory 如何知道已添加信用卡并需要插入。或者信用卡是如何被修改并需要更新的。
  • 无论如何,Person 都知道它被修改了。最简单的方法是删除所有链接的信用卡,然后保存当前状态。在我的项目中,我从数据库中检索数据作为 Update 方法的一部分,以检查应该添加、更新、删除或跳过哪些“孩子”。
【解决方案2】:

为了解决这个问题,我正在做的是向我的域对象添加一个接口:

interface IDirtyTracker {
    bool IsDirty {get;}
    void MarkClean();
    void MarkDirty();
}

基础DomainObject 类可以实现IDirtyTracker,然后存储库等可以使用IsDirty 来检查它是脏还是干净。

在每个进行更改的 setter 中:

void SetValue() {
    this._value = newValue;
    this.MarkDirty();
}

这不会为您提供细粒度检查,但它是一种避免在存储库级别进行一些不必要更新的简单方法。

为了使这更容易一些,可以添加一个GetPropertiesToIncludeInDirtyCheck 方法,该方法将检索需要检查的属性列表。

interface IDirtyTracker {
    IENumerable<Object> GetPropertiesToIncludeInDirtyCheck();
}

【讨论】:

    猜你喜欢
    • 2010-11-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-30
    • 1970-01-01
    • 2011-04-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多