存储库模式:如何延迟加载?或者,我应该拆分这个Aggregate吗?

时间:2009-01-19 16:10:58

标签: domain-driven-design repository-pattern lazy-loading aggregate ddd-repositories

我有一个具有编辑器和项目概念的域模型。

编辑拥有许多项目,项目不仅有编辑所有者,还有许多编辑成员。因此,编辑也有许多“加入”项目。

我正在采用DDD方法对此进行建模并使用Repository模式进行持久化。但是,我还没有很好地理解这个模式,以确定我应该怎么做。

我正在假设编辑器和项目可能在同一个聚合中,根是编辑器。因此,我可以获得一个编辑器,然后枚举其项目,并可以从那里枚举项目的成员编辑。

但是,如果我只被允许从我的存储库中检索编辑器,这是不是意味着当我获得拥有它们的编辑器时,我必须从存储库中加载所有项目?如果我想延迟加载成员编辑器,项目也需要对存储库的引用?

或者,如果我拆分聚合并拥有一个编辑器存储库和一个项目存储库,那么我应该如何处理两者之间的事务,例如将新项目添加到编辑器中?例如:

Editor e = new Editor("Editor Name");
editorRepository.Add(e);

Project p = e.CreateProject("Project Name");
projectRepository.Add(p);    // These two lines
editorRepository.Save(e);    // should be atomic

我是否误解了存储库模式的意图?

4 个答案:

答案 0 :(得分:30)

  

我是否误解了存储库模式的意图?

我要说“是的”,但要知道我和我曾经合作过的每个人都出于同样的原因问过同样的事情...... “你不是在想第四维,马蒂”

让我们稍微简化一下,先使用构造函数而不是Create方法:

Editor e = new Editor("Editor Name");
e = editorRepository.Add(e);

Project p = new Project("Project Name", e);
p = projectRepository.Add(p);

在下面,您的项目存储库始终将有效所有者(p.EditorId)存储到项目数据中,然后重新填充编辑器的项目,它将存在。这就是将所有必需属性放入构造函数中的好习惯。如果您不想传递整个对象,只需e.Id即可。

  

如果我想延迟加载成员编辑器,项目也需要对存储库的引用?

现在,关于如何根据需要重新填充编辑器的项目,您可以根据自己的需要选择几个选项。 Straight Repository说你想要:

IEnumerable<Project> list = projectRepository.GetAllProjects()
                                .Where(x => x.editorId == e.Id);

但是把它放在哪里?不在Project或Editor中,你是对的,或者他们必须访问存储库,这是不好的。上面的代码片段松散耦合,但不能自行重复使用。你刚刚达到了Repository Pattern的极限。

接下来是适用于您的应用程序的适配器层,具有共享的存储库源代码(StaticServiceWrapper)和某种EditorAdapter对象(或者Aggregate或者您称之为的任何对象),或者现在可以混合使用可以流畅地与任何和所有必要的存储库通信的扩展方法。我没有在生产系统中这样做,但为了向您展示一个简洁的例子:

public static class Aggregators
{
    // one to one, easy
    public static Editor GetOwner(this Project p)
    {
        return StaticServiceWrapper.editorRep.GetEditorById(p.editorId);
    }

    // one to many, medium
    public static IEnumerable<Project> GetProjects(this Editor e) 
    { 
        return StaticServiceWrapper.projectRep.GetAllProjects()
                .Where(x => x.editorId == e.Id);
    }

    // many to many, harder
    public static IEnumerable<Editor> GetMembers(this Project p)
    {
        var list = StaticServiceWrapper.projectMemberMap.GetAllMemberMaps()
                        .Where(x => x.projectId == p.projectId);

        foreach ( var item in list )
            yield return StaticServiceWrapper.editorRep.GetEditorById(item.editorId);
    }
}

基本上,一旦完成GetAll,GetById,添加,更新,删除对象存储库,就必须单独保留关联,并将对象/层层次结构向上移动到有趣的部分,如适配器和缓存以及业务逻辑(“哦,我的!”)。

答案 1 :(得分:4)

如何将职责分为EditorOwner和EditorMember?

在不了解您的域名的情况下,我认为他们有不同的职责 - 例如,EditorOwner可能相当丰富(并且可能是聚合根),但项目可能只需要知道有限的数量成员,所以EditorMember对象可能很轻。

这些域对象也可能与用户有关,但这可能在另一个上下文中。

这会有所帮助,还是让它变得更复杂?

答案 2 :(得分:3)

这取决于您的应用程序的需求。如果为给定的编辑器加载所有项目是一个大问题,那么尝试像Virtual Proxy这样的延迟加载模式。

关于懒惰加载项目的成员编辑,如果使用虚拟代理,我没有看到使用EditorRepository注入代理的问题,因为我不认为代理是域的一部分。

如果您拆分聚合,则可以将Unit of Work模式作为一个原子性解决方案进行调查。但是,这个问题并不是DDD独有的,我确信还有其他的交易行为解决方案。

答案 3 :(得分:0)

这里有2种不同的关系,一种用于所有权,另一种用于会员资格。

所有权关系很简单(每个项目一个所有者)。成员关系是多对多的(项目编辑很多,编辑很多项目)。

您可以在Project类上提供Owner属性,并在ProjectRepository上提供一个方法来获取特定Editor所拥有的所有项目。

对于许多关系,在Project类上提供Members属性,在ProjectRepository上提供一个方法,以将包含指定Editor的所有项目作为成员。

编辑和项目似乎也是实体,我可能会拆分聚合,但也许这些术语在您的上下文中具有特定含义,使其成为聚合的子实体。