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

发布于 2024-07-11 18:41:34 字数 681 浏览 6 评论 0原文

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

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

我采用 DDD 方法对此进行建模,并使用存储库模式进行持久化。 然而,我还没有充分理解这个模式,无法确定应该如何做到这一点。

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

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

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

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

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

I have a domain model that has the concept of an Editor and a Project.

An Editor owns a number of Projects, and a Project has not only an Editor owner, but also a number of Editor members. Therefore, an Editor also has a number of "joined" Projects.

I am taking a DDD approach to modelling this and using the Repository pattern for persistence. However, I don't grok the pattern well enough yet to determine how I should do this.

I'm working on the assumption that Editor and Project are potentially in the same aggregate, with the root being Editor. I can therefore get an Editor and then enumerate its Projects, and could from there enumerate the Projects' member Editors.

However, if I am only allowed to retrieve Editors from my repository, doesn't this mean I have to load all the Projects from the repository when I get the Editor that owns them? And if I want to lazy load the member Editors, the Project needs a reference to the repository as well?

Alternatively, if I split the aggregate and have an Editor repository and a Project repository, how should I handle a transaction across the two, such as when a new Project is added to an Editor? For example:

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

Am I misinterpreting the intent of the Repository pattern?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

年少掌心 2024-07-18 18:41:34

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

我会说“是的”,但要知道我和我共事过的每个人都出于同样的原因问过同样的事情......“你不是在第四维度思考,马蒂”。< /em>

让我们稍微简化一下,首先使用构造函数而不是 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);

但是把它放在哪里呢? 不在项目或编辑器内部,你是对的,否则他们将必须访问存储库,这是不好的。 上面的代码片段是松散耦合的,但不能单独重用。 您刚刚达到了存储库模式的极限。

接下来是您的应用程序的适配器层,具有共享的存储库源 (StaticServiceWrapper) 和某种 EditorAdapter 对象(或聚合或任何您所说的名称),或者现在您可以混合使用可以流畅地与任何和所有必要的存储库通信的扩展方法。 我还没有在生产系统中完全按照这种方式完成,但向您展示一个简洁的示例:

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、Add、Update、Remove 对象存储库完成,您就必须保留关联,并且继续沿着对象/层层次结构向上移动到适配器、缓存和业务逻辑等有趣的部分(“哦,天哪!”)。

Am I misinterpreting the intent of the Repository pattern?

I'm going to say "yeah", but know that me and every person I've worked with has asked the same thing for the same reason... "You're not thinking 4th dimensionally, Marty".

Let's simplify it a little and stick with constructors instead of Create methods first:

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

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

Underneath, your project repository is always storing a valid owner (p.EditorId) into the project data as it's created, and however you re-populate an editor's projects, it will be there. This is why it's a good practice to put all required properties into constructors. If you don't want to pass the whole object, just the e.Id will do.

And if I want to lazy load the member Editors, the Project needs a reference to the repository as well?

Now, as to how to re-populate an editor's projects on demand, you have a couple of choices depending on what you're going for. Straight Repository says you want:

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

But where to put it? Not inside Project, or Editor, you're right, or they will have to get access to repositories and that's no good. The above snippet is loosely coupled, but isn't reusable on its own. You've just reached the limits of Repository Pattern.

Next up is an Adapter Layer for your application, with a shared source of repositories (StaticServiceWrapper) and either some sort of EditorAdapter object (or Aggregate or whatever you'd call them) or now you can mix in extension methods that can talk to any and all necessary repositories fluently. I haven't done it exactly this way in a production system, but to show you a concise example:

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);
    }
}

Basically, once your GetAll,GetById,Add,Update,Remove Object Repository is done, you've got to leave the associations alone and move on up the object/layer hierarchy to the fun parts like Adapters and Caches and Business Logic ("Oh, my!").

很快妥协 2024-07-18 18:41:34

将职责划分为 EditorOwner 和 EditorMember 怎么样?

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

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

这对事情有帮助吗,还是只会让事情变得更复杂?

How about splitting responsibilities into an EditorOwner and an EditorMember?

Without knowing your domain, I'd imagine they'd have different responsibilities - for example, the EditorOwner might be quite rich (and could be the aggregate root), but the Project may only need to know a limited amount about its members, so the EditorMember object may be quite light.

These domain objects may also relate to Users, but that would be in another context.

Does that help things, or just make it more complicated?

不即不离 2024-07-18 18:41:34

这取决于您的应用程序的需求。 如果加载给定编辑器的所有项目是一个大问题,请尝试延迟加载模式,例如 虚拟代理

关于延迟加载项目的成员编辑,如果您使用虚拟代理,我不会发现使用 EditorRepository 注入代理有问题,因为我不认为代理是域的一部分。

如果您拆分聚合,则可以研究工作单元模式作为原子性的一种解决方案。 不过,这个问题并不是 DDD 所独有的,我确信还有其他针对事务行为的解决方案。

It depends on your application's needs. If it is a big problem to load all of the Projects for a given Editor, then try a lazy loading pattern like a Virtual Proxy.

Regarding lazily loading the member Editors of a Project, if you use Virtual Proxy, I don't see a problem injecting the proxy with the EditorRepository since I don't consider the proxy to be part of the domain.

If you split up the Aggregate, you can investigate the Unit of Work pattern as one solution to atomicity. This problem, though, is not unique to DDD and I'm sure there are other solutions for transactional behavior.

预谋 2024-07-18 18:41:34

这里有两种不同的关系,一种是所有权关系,一种是成员关系。

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

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

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

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

Here you have 2 different relationships, one for ownership and one for membership.

The ownership relation is a simple one to many (one owner for each project). The membership relation is many to many (many Editors by project, many projects by editor).

You could provide a Owner property on the Project class, and provide a method on the ProjectRepository to get all projects owned by a specific Editor.

For the many relationship, provide a Members property on the Project class, and a method on the ProjectRepository to get all projects containing specified Editor as member.

It also seems that Editors and Projects are entities, I would probably split the aggregate, but perhaps those terms have a specific meaning in your context that make it subentities of an aggregate.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文