【问题标题】:Is this a ddd anti-pattern?这是 ddd 反模式吗?
【发布时间】:2013-02-22 23:39:51
【问题描述】:

像这样将存储库接口注入到Entity对象中是否违反了Persistance igorance。通过不使用界面,我清楚地看到了问题,但是使用界面时真的有问题吗?下面的代码是好的还是坏的模式?为什么?

public class Contact
{
    private readonly IAddressRepository _addressRepository;

    public Contact(IAddressRepository addressRepository)
    {
        _addressRepository = addressRepository;
    }

    private IEnumerable<Address> _addressBook;
    public IEnumerable<Address> AddressBook
    {
        get
        {
            if(_addressBook == null)
            {
               _addressBook = _addressRepository.GetAddresses(this.Id);
            }
            return _addressBook;
        }
    }
}

【问题讨论】:

    标签: repository domain-driven-design entities


    【解决方案1】:

    这不是一个好主意,但在某些有限的情况下可能没问题。我对您的模型有点困惑,因为我很难相信 Address 是您的聚合根,因此拥有一个完整的地址存储库并不常见。根据您的示例,您可能实际上使用的是表数据网关或 dao 而不是存储库。

    我更喜欢使用数据映射器来解决这个问题(ORM 或类似的解决方案)。基本上,我会利用我的 ORM 将地址簿视为聚合根“联系人”的延迟加载属性。这样做的好处是,只要实体绑定到会话,就可以保存您的更改。

    如果我不使用 ORM,我仍然希望具体的联系人存储库实现设置 AddressBook 后备存储(列表或其他)的属性。我可能将存储库设置为一个代理对象,该对象确实知道其他数据存储,并按需加载它。

    【讨论】:

    • 该代码是一个纯示例,并非基于任何真实代码,但我同意 AddressRepository 是一个不好的示例
    • @JasonTrue,地址作为聚合根有什么问题?如果多个联系人共享同一个地址(例如,来自同一公司但不拥有地址所有权的联系人),您将如何建模?如果他们有唯一的身份,他们就不能成为 VO。
    【解决方案2】:

    您可以从外部注入加载函数。 .NET 4.0 中的新 Lazy&lt;T&gt; 类型就派上用场了:

        public Contact(Lazy<IEnumerable<Address>> addressBook)
        {
            _addressBook = addressBook;
        }
    
        private Lazy<IEnumerable<Address>> _addressBook;
        public IEnumerable<Address> AddressBook
        {
            get { return this._addressBook.Value; }
        }
    

    还请注意,当您从查询提供程序获取 IEnumerable&lt;T&gt;s 时,它们可能本质上是惰性的。但对于任何其他类型,您可以使用Lazy&lt;T&gt;

    【讨论】:

      【解决方案3】:

      通常,当您遵循 DDD 时,您总是使用 整个 聚合进行操作。存储库始终为您返回一个完全加载的聚合根。

      在您的示例中编写代码没有多大意义(至少在 DDD 中)。 Contact 聚合将始终包含所有地址(如果它的行为需要它们,我怀疑这是诚实的)。

      因此,通常 ContactRepository 假定为您构建整个 Contact 聚合,其中 Address 是一个实体,或者很可能是此聚合内的一个值对象。

      因为 Address 是一个实体/值对象,属于(因此由其管理)Contact 聚合,所以它没有自己的存储库,因为您不应该管理属于此聚合之外的聚合的实体。

      Resume:总是加载整个 Contact 并调用它的行为方法来处理它的状态。

      【讨论】:

        【解决方案4】:

        自从我提出这个问题以来已经有 2 年了,而且这个问题有些被误解了,我将尝试自己回答。

        改写的问题: “业务实体类应该完全不了解持久性吗?”

        我认为实体类应该完全不知道持久性,因为您将在代码库中的许多地方实例化它们,因此总是必须将 Repository 类注入实体构造函数很快就会变得混乱,而且看起来也不是很干净。如果您需要注入多个存储库,这将变得更加明显。因此,我总是使用单独的处理程序/服务类来为实体执行持久性工作。这些类的实例化频率要低得多,您通常可以更好地控制这种情况发生的地点和时间。实体类尽可能保持轻量级。

        我现在总是有 1 个存储库 pr 聚合根,如果在从存储库中获取实体时需要一些额外的业务逻辑,我通常会为聚合根创建 1 个服务类。

        通过对问题中的代码进行调整示例,因为这是一个不好的示例,我现在会这样做:

        代替:

        public class Contact
        {
            private readonly IContactRepository _contactRepository;
        
            public Contact(IContactRepository contactRepository)
            {
                _contactRepository = contactRepository;
            }
        
            public void Save()
            {
                _contactRepository.Save(this);
            }
        }
        

        我是这样做的:

        public class Contact
        {
        
        }
        
        public class ContactService
        {
            private readonly IContactRepository _contactRepository;
        
            public ContactService(IContactRepository contactRepository)
            {
                _contactRepository = contactRepository;
            }
        
            public void Save(Contact contact)
            {
                _contactRepository.Save(contact);
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2023-03-11
          • 2011-01-28
          • 1970-01-01
          • 2015-08-25
          • 1970-01-01
          • 2010-10-20
          • 2020-07-11
          相关资源
          最近更新 更多