1

I'm trying to create repositories in my app. The problem is mainly with 2 entities:

  • Workspace
  • Member

a Workspace has many members, while a Member is only related to one Workspace (so a member exists only within a workspace).

Workspace has the functionality to add members and check if members exist:

class Workspace(private val members: HashSet<Member>) {
  fun addMember(memberId: MemberId, name: MemberName, email: Email): Member {
    // ...
  }

  fun hasMember(memberId: MemberId): Boolean {
    // ...
  }
}

What I am not really sure about is wether to have 2 separate repositories [WorkspaceRepository / MemberRepository], or should the WorkspaceRepository be responsible for saving and populating members in the workspace?

If there will be a MemberRepository, how will be able to use the functionality in the Workspace? what will prevent me from just moving the business logic outside of the Workspace?

And if WorkspaceRepository will be responsible for everything, isn't it a bit much loading all the members everytime I load a repository?

Thank you.

2
  • Which approach is most suitable for your particular application? Nov 22, 2022 at 12:50
  • @RobertHarvey in this particular case I'm leaning towards saving/loading members separately with another repository, but there will be a lot of access control and rules for members in the workspace so I don't want to move all of this logic to application services as well. Nov 22, 2022 at 12:58

2 Answers 2

2

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.

4
  • Thank you for your answer, this made things more clear for me. To answer your questions: 1. Members are controlled from the Workspace, so to create/delete a member this is done from the workspace itself. 2. When a Member is removed from the workspace it's also removed from the datastore since Members cannot exist on their own. Having those answers, do you have a recommended way on what to do in this scenario? Nov 22, 2022 at 18:03
  • Also if I went with having a WorkspaceRepository only that's handling the Members as well, how can I know when to persist a new member, or which member to delete if the only method I have is save(workspace)? Or should I expose also functions as saveMember(member) and deleteMember(member)? Nov 22, 2022 at 18:07
  • You can declare these methods, but you don't make them public. You know, the walkaround you don't want the API consumer to know or exploit. Or the alternative is, to remove all the members of the given workspace and repopulate the member list with those in the object. All in the same transaction. This way, you avoid comparing what do I have in memory with what do I have in DB.
    – Laiv
    Nov 23, 2022 at 8:29
  • You will delete members one by one if you have something to do first, like solving foreign keys (e.g other tables pointing to Member table or other workspaces the member belongs to). If Member is pointed by other tables or workspaces, the best you can do is save these relationships in a dedicated table WorkspaceMembers. So when you delete members from workspaces, you delete entries of this table, leaving alone the table Members and not messing with FK.
    – Laiv
    Nov 23, 2022 at 8:34
3

I think the question you should ask is: can a Member live outside a Workspace?

If a Member can exist outside a Workspace, then I would opt for separate repositories, and would assign a Member to a Workspace.

If a Member is tied to an individual Workspace, and cannot exist outside it, I would use the WorkspaceRepository to create it.

For example, let's imagine an organization app with two entities Checklist, and ChecklistItem. The existence of a CheckListItem alone doesn't make sense. It needs to belong to a CheckList to have any meaning.

On the other hand, let us consider an e-learning app, like Coursera, with the entities Course, and Student. Right now the Student can be enrolled in multiple Courses, but at some moment they might as well not have any enrollments. In this case, the Student can live without the existence of any Courses.

1
  • Thanks for your answer. I get this separation and in my case the Member can't exist outside the Workspace. Can you give me example on how the repository should look like? is it going to have save(workspace) that's responsible also for saving the members? If so, how would I know which members are newly added to avoid calling the db with pre-existing data? Nov 22, 2022 at 15:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.