views:

1206

answers:

4

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?

+2  A: 

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.

moffdub
+1  A: 

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?

Michael Hart
A: 

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.

Think Before Coding
+10  A: 

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

ifatree