【问题标题】:How to decouple repository and entities如何解耦存储库和实体
【发布时间】:2012-06-04 19:27:19
【问题描述】:

这是一个关于领域模型设计的问题。

假设对于涉及用户和组的域设计,我们需要实现以下接口:

interface IUser
{
    string Name{get;}
    DateTime DOB {get;}
}

interface IGroup
{
    string Name {get;}
    bool IsUserInGroup(IUser user); // #1
    void IncludeUser(IUser user);   // #2
    void ExcludeUser(IUser user);   // #3
}

interface IUserRepository
{
    IUser Create(string name);
    IUser GetByName(string name);
    void Remove(IUser user);
    void Save(IUser user);
}

interface IGroupRepository
{
    IGroup Create(string name);
    IGroup GetByName(string name);
    void Remove(IGroup group);
    void Save(IGroup group);
}

棘手的一点是实现#1 #2 和#3,同时保持实体类(User、Group)与存储库类(UserRepository、GroupRepository)解耦。

另一个需要考虑的技术问题是大多数 RMDB 系统不实现多对多关系,并且在实践中总是有一个单独的表(例如,UserGroupAssociation)来记录每个记录通过外键关联一个用户和一个组。我想从域接口中隐藏这个实现细节,并通过成员 #1 #2 和 #3 公开等效逻辑。

调用#2 和#3 的效果不应该持续到有问题的组对象被保存(即传递给存储库对象的 Save() 方法)

你平时是怎么做的?

【问题讨论】:

    标签: model-view-controller model ddd-repositories


    【解决方案1】:

    我不这样做。我的Repository 对象与它们相关的聚合的根紧密耦合,并且(顺便说一句)我不会费心为我的域模型对象创建接口,除非我发现我有充分的理由这样做- 你这样做有什么特别的理由吗?

    我没有遇到任何Repository 示例不使用存储库类中的实体实现类型(例如this one),并且想不出使用接口的任何真正优势。接口通过在测试时更容易模拟系统的整个层来赢得基础设施组件(如Repository)的保留,但使用域对象的接口不会获得相同类型的优势。

    也许要真正回答这个问题……

    我从来没有一个域对象访问Repository - 毕竟域对象应该代表现实生活中域中的某些东西,而存储库是现实生活中不存在的基础设施组件,那么为什么要域对象知道一个吗?

    对于将User 添加到Group 的具体示例,我将使用Service Layer 类,并执行以下操作:

    public class UserService
    {
        private readonly IGroupRepository _groupRepository;
        private readonly IUserRepository _userRepository;
    
        public UserService(
            IGroupRepository groupRepository,
            IUserRepository userRepository)
        {
            this._groupRepository = groupRepository;
            this._userRepository = userRepository;
        }
    
        public void IncludeUserInGroup(string groupName, string userName)
        {
            var group = this._groupRepository.FindByName(groupName);
            var user = this._userRepository.FindByName(userName);
    
            group.IncludeUser(user);
    
            this._userRepository.SaveChanges();
        }
    }
    
    public class User
    {
        public void AddToGroup(Group group)
        {
            this.Groups.Add(group);
        }
    
        public void RemoveFromGroup(Group group)
        {
            this.Groups.Remove(group);
        }
    }
    

    需要注意的几点:

    1. 为避免在将 User 添加到 Group 时延迟加载大量 Users,我已将 Group 管理方法移至 User - 取决于您的行为量实际上有Group,你甚至可以考虑把它变成一个枚举而不是一个类。请注意,如果您使用带有 FixupCollections 的实体框架 POCO T4 模板,这仍会延迟加载 Users 中的所有 Group,但您可以通过一种或另一种方式解决这个问题: )

    2. Service Layer 类将实现Create() 方法,就像您在存储库中拥有的那样。存储库将具有 Add 方法、Find 方法和 SaveChanges() 方法。 Add 会将服务层创建的对象添加到对象上下文中。

    3. 所有Repository 类都将设置为使用相同的底层、请求范围的对象上下文,因此您调用哪个SaveChanges() 并不重要。

    4. SaveChanges() 将导致在该请求期间发生在对象上的所有更改都被保存,例如 User 将新的 Group 添加到其 Groups 集合中。

    最后,将实体与存储库和其他基础架构组件分离的另一种好技术是Domain Events

    【讨论】:

    • 接口的使用是为了呈现一个更高层次的概念,而不仅仅是一个读/写的属性桶。例如,不是class User{public byte[] PasswordHash{get;set;} } 而是interface IUser{bool VerifyPassword(string password);} 不是更好吗?
    • 在那个例子中,你不能将PasswordHash 属性设为私有(或受保护,如果你受到ORM 的限制)? User 仍然会有 VerifyPassword() 的实现,所以它不仅仅是一堆属性。
    • 对于简单的方法,这是完全正确的。但是对于像 IncludeUser() 和 ExcludeUser() (示例 #2 和 #3)这样的其他实现需要访问存储库对象,而不一定是它自己的存储库对象。在 IncludeUser() 的情况下,Group 类将需要与 GroupRepository、UserGroupAssociationRepository 对话。然而,让实体知道存储库是非常不可取的耦合。
    • 史蒂夫这几乎和我想要的一样好。剩下的唯一灰色区域是您的示例中的 Group.Users 集合。如果一个组可以包含大量用户,那么只在需要时延迟加载集合当然是一个好主意,但它需要能够从集合中添加和删除实体。这是如何实现的?
    • 我已经参考延迟加载更新了我的答案。乐于助人:)
    猜你喜欢
    • 1970-01-01
    • 2013-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-26
    相关资源
    最近更新 更多