Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 348
Giraffe Avatar asked Jan 19 '09 16:01

Giraffe


3 Answers

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

like image 181
ifatree Avatar answered Oct 16 '22 06:10

ifatree


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?

like image 5
Michael Hart Avatar answered Oct 16 '22 06:10

Michael Hart


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.

like image 3
moffdub Avatar answered Oct 16 '22 04:10

moffdub