Repository Pattern: how to Lazy Load? or, Should I split this Aggregate?
T

4

43

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?

Township answered 19/1, 2009 at 16:10 Comment(1)
You might want to have a look at my related question: stackoverflow.com/q/20820302/253098Ebneter
A
30

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!").

Acadian answered 14/5, 2009 at 4:40 Comment(2)
If the underlying implementation of IRepository is a database, and you have a very big amount of X entity. Is it still ok to use GetAll() on the repository, and then perform a LINQ where query on it the entire set? What if that is too slow, and you'd rather do the query at the DB level, how would you do that with the Repository?Fro
Using FindAll() will not cause the SQL query to be fired instantly. This only happens when you convert it to a List, a single element (for example with .FirstOrDefault() or you do a foreach loop on it. So you can do all kinds of Linq stuff with it before you execute your query and the results are fetched from your DB.Bawdy
L
5

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?

Lure answered 23/1, 2009 at 4:55 Comment(0)
K
3

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.

Keddah answered 23/1, 2009 at 2:45 Comment(0)
K
0

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.

Kuster answered 11/2, 2009 at 13:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.