What I am not sure about is whether to have two separated repositories [WorkspaceRepository / MemberRepository]
As usual, the answer is depends.
Ok, the relationship Workspace
<- Member
is enforced, programmatically, from Workspace
, which acts like an aggregation root. That's fine. But where do Members
come from? When or how are members created, edited and listed?
When we add members to workspaces, are we creating members anew? When we remove them from workspaces, are we removing Member
from the data store too? Or are we removing the relationship Workspace - member
instead? As @Cacnode points out, what's the Memeber
life cycle?
You have to answer questions like these to figure out whether MemberRepository
is required and its features.
what will prevent me from just moving the business logic outside of the Workspace?
Nothing can prevent a developer from moving/copying a piece of code from component A to B. What you can do is guide the API1 consumer by not allowing walkarounds. To this end, encapsulation is key.
If there will be a MemberRepository, how will be able to use the functionality in the Workspace?
It doesn't. MemberRepository
exists to manage members
not workspaces
. It knows nothing about workspaces2.
or should the WorkspaceRepository be responsible for saving and populating members in the workspace?
Why not?
Repositories are abstractions (ideally, proposed by the core). Think about them as interfaces. The key, then, is in the implementation.
public class WorkspaceDBRepository implements WorkspaceRepository {
private Dao<Workspace> workspaceDao; //<--- implementation detail
private Dao<Member> memberDao; //<--- implementation detail
}
public class MemberDBRepository implements MemberRepository {
private Dao<Member> memberDao;//<--- implementation detail
}
Let's say we finally want the list of workspaces a Member
belongs to. A one-to-many join that most of ORMs implement out of the box.
public class MemberDBRepository implements MemberRepository {
private Dao<Member> memberDao;//<--- implementation detail
private ReadOnlyDao<Workspace> workspaceDao;//<--- implementation detail
//...
//...
//no public method with Workspace on its signature. Ever.
}
These are only examples. Probably not the most elegant ones, but they illustrate my point about abstraction vs implementation details.
1: Application *Public Interface
2: Unless you implement Member.workspaces
. That's the kind of walkaround I was talking about.