D.Tomov
D.Tomov

Reputation: 1158

How to add to a Set of entities when doing Many-to-many

I have the following entities with a many-to-many releationship

User entity

@Entity
@Table(name = "users")
public class User {
    ...
    @ManyToMany
    @JoinTable(
            name = "team_members",
            joinColumns = @JoinColumn(name = "team_id"),
            inverseJoinColumns = @JoinColumn(name = "user_id"))
    private Set<Team> teams;

Team entity

@Entity
@Table(name = "teams")
public class Team {
    ...
    @ManyToMany
    @JoinTable(
            name = "team_members",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "team_id"))
    private Set<User> members;
}

And I have this UserService

public interface UserService {
    UserDTO getById(Integer userId);
    ...
}

I want to implement a method inside a TeamService like this:

@Override
@Transactional
public TeamDTO addMemberToTeam(Integer teamId, Integer userId, User currentUser) {
    checkCurrentUserIsAdmin(currentUser);
    User user = userService.getById(userId); <-------- PROBLEM LINE
    Team team = teamRepository.findById(teamId)
            .orElseThrow(() -> new RuntimeException("No such team"));
    team.getMembers().add(user);

    return TeamDTO.fromTeam(teamRepository.save(team));
}

Problem is I have the service to return UserDTO as it should, but in order to make the releationship work I need a User.

Possible solutions I could think of:

Solution 1:

Have another method in UserService which explicitly returns User like:

User getUserById(Integer userId);

But won't this brake the idea of UserService only returning UserDTOs. Is there a standard way of doing this and still follow best practices of Service interfaces to work with DTOs.

Solution 2:

Have another method in UserService like:

@Override
public void addUserToTeam(Integer userId, Team team) {
    User user = userRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("No such user"));
    user.getTeams().add(team);
    team.getMembers().add(user);
    userRepository.save(user);
}

And change the method in TeamService to this:

@Override
@Transactional
public TeamDTO addMemberToTeam(Integer teamId, Integer userId, User currentUser) {
    checkCurrentUserIsAdmin(currentUser);
    Team team = teamRepository.findById(teamId)
            .orElseThrow(() -> new RuntimeException("No such team"));

    userService.addUserToTeam(userId,team); <--- THIS NEW METHOD CALL

    return TeamDTO.fromTeam(teamRepository.save(team));
}

This seems like a better solution, but I still have a feeling I am over complicating it.

Which would be the better solution in my case?

Upvotes: 0

Views: 50

Answers (1)

Mr.J4mes
Mr.J4mes

Reputation: 9266

If I understand correctly, the best practice you're trying to follow have 1 @Service owns an @Entity, which is why you're trying so hard not to use userRepository directly inside TeamService.

IMHO, DTO is only required when you're communicating with external parties like when you have a REST endpoint that returns a UserDTO to the consumer instead of returning the @Entity itself. Within the boundary of your own application and services, I think it's OK to use repositories wherever you need it instead of making it difficult by creating a hard line in the sand for yourself.

Just in case you still want to use the UserDTO to communicate between services and with external parties, I suggest this.

public class UserDTO {
   ... 
   // your fields 
   ...

   @JsonIgnore
   private User userRecord;
}

This way, your services will still have access to the actual @Entity and when you return this object to API consumers, they will not see that field.

Upvotes: 1

Related Questions