Troubleshooting: 무엇이 문제였는가?/Spring 심화 프로젝트

6단계: 예외 처리 메서드는 어떻게? (수정 중)

writingforever162 2025. 1. 6. 13:42

1. [문제 인식 및 정의]

 

q1. 중복이 왜 안 좋은가???

package org.example.expert.domain.manager.service;

import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.example.expert.domain.common.dto.AuthUser;
import org.example.expert.domain.common.exception.InvalidRequestException;
import org.example.expert.domain.manager.entity.Manager;
import org.example.expert.domain.manager.repository.ManagerRepository;
import org.example.expert.domain.todo.entity.Todo;
import org.example.expert.domain.todo.repository.TodoRepository;
import org.example.expert.domain.user.entity.User;
import org.example.expert.domain.user.repository.UserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

@Service
@RequiredArgsConstructor
public class ManagerService {

  private final ManagerRepository managerRepository;
  private final UserRepository userRepository;
  private final TodoRepository todoRepository;

  @Transactional
  public Manager createManager(
      AuthUser authUser,
      long todoId,
      long userId
  ) {

    User userFromAuth = User.fromAuthUser(authUser);

    // [일정 찾기 (1/3)]
    Todo foundTodo = todoRepository.findById(todoId)
        .orElseThrow(
            () -> new InvalidRequestException(
                "Todo is not found"
            )
        );

    boolean isUserInvalid = !ObjectUtils.nullSafeEquals(
        userFromAuth.getId(),
        foundTodo.getUser().getId()
    );

    if (isUserInvalid) {
      throw new InvalidRequestException(
          "User is invalid"
      );
    }

    // [사용자 찾기 (1/2)]
    User foundUser = userRepository.findById(userId)
        .orElseThrow(
            () -> new InvalidRequestException(
                "User is not found"
            )
        );

    boolean isSelfAssignment = ObjectUtils.nullSafeEquals(
        userFromAuth.getId(),
        foundUser.getId()
    );

    if (isSelfAssignment) {
      throw new InvalidRequestException(
          "Todo creator cannot assign self as manager"
      );
    }

    Manager managerToSave = new Manager(foundUser, foundTodo);

    Manager savedManager = managerRepository.save(managerToSave);

    return savedManager;
  }

  @Transactional(readOnly = true)
  public List<Manager> readAllManagers(
      long todoId
  ) {
    // [일정 찾기 (2/3)]
    Todo foundTodo = todoRepository.findById(todoId)
        .orElseThrow(
            () -> new InvalidRequestException(
                "Todo is not found"
            )
        );

    List<Manager> managerList = new ArrayList<>();

    managerList = managerRepository
        .findAllByTodoId(foundTodo.getId());

    return managerList;
  }

  @Transactional
  public void deleteManager(
      long userId,
      long todoId,
      long managerId
  ) {
    // [사용자 찾기 (2/2)]
    User foundUser = userRepository.findById(userId)
        .orElseThrow(
            () -> new InvalidRequestException(
                "User is not found"
            )
        );

    // [일정 찾기 (3/3)]
    Todo foundTodo = todoRepository.findById(todoId)
        .orElseThrow(
            () -> new InvalidRequestException(
                "Todo is not found"
            )
        );

    boolean isInvalidUser = foundTodo.getUser() == null
        || !ObjectUtils.nullSafeEquals(
        foundUser.getId(),
        foundTodo.getUser().getId()
    );

    if (isInvalidUser) {
      throw new InvalidRequestException(
          "User who created todo is invalid"
      );
    }

    // [관리자 찾기 (1/1)]
    Manager foundManager = managerRepository.findById(managerId)
        .orElseThrow(
            () -> new InvalidRequestException(
                "Manager is not found"
            )
        );

    boolean isManagerMismatch = !ObjectUtils.nullSafeEquals(
        foundTodo.getId(),
        foundManager.getTodo().getId()
    );

    if (isManagerMismatch) {
      throw new InvalidRequestException(
          "Manager is not assigned to todo"
      );
    }

    managerRepository.delete(foundManager);
  }
}

2. [해결 방안]

2-1. [의사 결정 과정]

(1) 방안 1: private 메서드

- 장점: 캡슐화 가능, 외부에서 접근할 필요가 없으므로, 이 클래스에서만 쓸 거라면 private 메서드

package org.example.expert.domain.manager.service;

import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.example.expert.domain.common.dto.AuthUser;
import org.example.expert.domain.common.exception.InvalidRequestException;
import org.example.expert.domain.manager.entity.Manager;
import org.example.expert.domain.manager.repository.ManagerRepository;
import org.example.expert.domain.todo.entity.Todo;
import org.example.expert.domain.todo.repository.TodoRepository;
import org.example.expert.domain.user.entity.User;
import org.example.expert.domain.user.repository.UserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

@Service
@RequiredArgsConstructor
public class ManagerService {

  private final ManagerRepository managerRepository;
  private final UserRepository userRepository;
  private final TodoRepository todoRepository;

  @Transactional
  public Manager createManager(
      AuthUser authUser,
      long todoId,
      long userId
  ) {

    User userFromAuth = User.fromAuthUser(authUser);

    Todo foundTodo = findTodo(todoId);

    boolean isUserInvalid = !ObjectUtils.nullSafeEquals(
        userFromAuth.getId(),
        foundTodo.getUser().getId()
    );

    if (isUserInvalid) {
      throw new InvalidRequestException(
          "User is invalid"
      );
    }

    User foundUser = findUser(userId);

    boolean isSelfAssignment = ObjectUtils.nullSafeEquals(
        userFromAuth.getId(),
        foundUser.getId()
    );

    if (isSelfAssignment) {
      throw new InvalidRequestException(
          "Todo creator cannot assign self as manager"
      );
    }

    Manager managerToSave = new Manager(foundUser, foundTodo);

    Manager savedManager = managerRepository.save(managerToSave);

    return savedManager;
  }

  @Transactional(readOnly = true)
  public List<Manager> readAllManagers(
      long todoId
  ) {
    Todo foundTodo = findTodo(todoId);

    List<Manager> managerList = new ArrayList<>();

    managerList = managerRepository
        .findAllByTodoId(foundTodo.getId());

    return managerList;
  }

  @Transactional
  public void deleteManager(
      long userId,
      long todoId,
      long managerId
  ) {
    // [사용자 찾기 (2/2)]
    User foundUser = findUser(userId);

    // [일정 찾기 (3/3)]
    Todo foundTodo = findTodo(todoId);

    boolean isInvalidUser = foundTodo.getUser() == null
        || !ObjectUtils.nullSafeEquals(
        foundUser.getId(),
        foundTodo.getUser().getId()
    );

    if (isInvalidUser) {
      throw new InvalidRequestException(
          "User who created todo is invalid"
      );
    }

    Manager foundManager = findManager(managerId);

    boolean isManagerMismatch = !ObjectUtils.nullSafeEquals(
        foundTodo.getId(),
        foundManager.getTodo().getId()
    );

    if (isManagerMismatch) {
      throw new InvalidRequestException(
          "Manager is not assigned to todo"
      );
    }

    managerRepository.delete(foundManager);
  }

  private Manager findManager(long managerId) {
    return managerRepository.findById(managerId)
        .orElseThrow(
            () -> new InvalidRequestException(
                "Manager is not found"
            )
        );
  }

  private Todo findTodo(long todoId) {
    return todoRepository.findById(todoId)
        .orElseThrow(
            () -> new InvalidRequestException(
                "Todo is not found"
            )
        );
  }

  private User findUser(long userId) {
    return userRepository.findById(userId)
        .orElseThrow(
            () -> new InvalidRequestException(
                "User is not found"
            )
        );
  }
}

(2) 방안 2: utility 메서드 

- 장점: 외부에서 클래스를 만드냐. 외부에서 접근을 해야 하는가, 말아야 하는가, 확장성이 넓으면 / 다만 기능 추가는 필요하다. 

package org.example.expert.config;

import java.util.Optional;
import org.example.expert.domain.common.exception.InvalidRequestException;

public class EntityFinderUtil {

  // 레포지토리를 넘겨보도록, 옵셔널 대신에
  public static <T> T findEntityById(
      Optional<T> entityOptional,
      Class<T> entityClass
  ) {
    return entityOptional.orElseThrow(
        () -> new InvalidRequestException(
            getEntityName(entityClass) + " is not found")
    );
  }

  private static <T> String getEntityName(Class<T> entityClass) {
    return entityClass.getSimpleName();
  }
}

(3) abstract 클래스로 baseService : 코드 복잡도가 올라간다. (러닝 커브) + 서비스가 서비스를 바라봐서 아쉬워진다. 구조상 아쉬움 -> 같은 계층끼리 참조해도 문제는 없으나 순환 참조가 걸릴 수 있다.

 

2-2. [해결 과정]

package org.example.expert.config;

import java.util.Optional;
import org.example.expert.domain.common.exception.InvalidRequestException;

public class EntityFinderUtil {

  public static <T> T findEntityById(Optional<T> entityOptional) {

    return entityOptional.orElseThrow(
        () -> new InvalidRequestException(getEntityName(entityOptional) + " is not found"));
  }

  private static <T> String getEntityName(Optional<T> entityOptional) {
    return entityOptional
        .map(entity -> entity.getClass().getSimpleName())
        .orElse("Entity");
  }
}

 

package org.example.expert.config;

import java.util.Optional;
import org.example.expert.domain.common.exception.InvalidRequestException;

public class EntityFinderUtil {

  // todo 레포지토리를 넘겨보도록, 옵셔널 대신에
  public static <T> T findEntityById(
      Optional<T> entityOptional,
      Class<T> entityClass
  ) {
    return entityOptional.orElseThrow(
        () -> new InvalidRequestException(
            getEntityName(entityClass) + " is not found")
    );
  }

  private static <T> String getEntityName(Class<T> entityClass) {
    return entityClass.getSimpleName();
  }
}

 

 

 

package org.example.expert.domain.comment.service;

import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.example.expert.config.EntityFinderUtil;
import org.example.expert.domain.comment.entity.Comment;
import org.example.expert.domain.comment.repository.CommentRepository;
import org.example.expert.domain.common.dto.AuthUser;
import org.example.expert.domain.todo.entity.Todo;
import org.example.expert.domain.todo.repository.TodoRepository;
import org.example.expert.domain.user.entity.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class CommentService {

  private final TodoRepository todoRepository;
  private final CommentRepository commentRepository;

  @Transactional
  public Comment createComment(
      AuthUser authUser,
      long todoId,
      String contents
  ) {
    User userFromAuth = User.fromAuthUser(authUser);

    Todo foundTodo = EntityFinderUtil.findEntityById(
        todoRepository.findById(todoId),
        Todo.class
    );
    
    Comment commentToSave = new Comment(
        contents,
        userFromAuth,
        foundTodo
    );

    Comment savedComment = commentRepository.save(commentToSave);

    return savedComment;
  }

  @Transactional(readOnly = true)
  public List<Comment> readAllComments(long todoId) {
    List<Comment> commentList = new ArrayList<>();

    commentList = commentRepository.findAllByTodoId(todoId);

    return commentList;
  }
}
package org.example.expert.domain.manager.service;

import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.example.expert.config.EntityFinderUtil;
import org.example.expert.domain.common.dto.AuthUser;
import org.example.expert.domain.common.exception.InvalidRequestException;
import org.example.expert.domain.manager.entity.Manager;
import org.example.expert.domain.manager.repository.ManagerRepository;
import org.example.expert.domain.todo.entity.Todo;
import org.example.expert.domain.todo.repository.TodoRepository;
import org.example.expert.domain.user.entity.User;
import org.example.expert.domain.user.repository.UserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

@Service
@RequiredArgsConstructor
public class ManagerService {

  private final ManagerRepository managerRepository;
  private final UserRepository userRepository;
  private final TodoRepository todoRepository;

  @Transactional
  public Manager createManager(
      AuthUser authUser,
      long todoId,
      long userId
  ) {

    User userFromAuth = User.fromAuthUser(authUser);

    Todo foundTodo = EntityFinderUtil.findEntityById(
        todoRepository.findById(todoId),
        Todo.class
    );

    boolean isUserInvalid = !ObjectUtils.nullSafeEquals(
        userFromAuth.getId(),
        foundTodo.getUser().getId()
    );

    if (isUserInvalid) {
      throw new InvalidRequestException(
          "User is invalid"
      );
    }

    User foundUser = EntityFinderUtil.findEntityById(
        userRepository.findById(userId),
        User.class
    );

    boolean isSelfAssignment = ObjectUtils.nullSafeEquals(
        userFromAuth.getId(),
        foundUser.getId()
    );

    if (isSelfAssignment) {
      throw new InvalidRequestException(
          "Todo creator cannot assign self as manager"
      );
    }

    Manager managerToSave = new Manager(foundUser, foundTodo);

    Manager savedManager = managerRepository.save(managerToSave);

    return savedManager;
  }

  @Transactional(readOnly = true)
  public List<Manager> readAllManagers(
      long todoId
  ) {
    Todo foundTodo = EntityFinderUtil.findEntityById(
        todoRepository.findById(todoId),
        Todo.class
    );

    List<Manager> managerList = new ArrayList<>();

    managerList = managerRepository
        .findAllByTodoId(foundTodo.getId());

    return managerList;
  }

  @Transactional
  public void deleteManager(
      long userId,
      long todoId,
      long managerId
  ) {
    User foundUser = EntityFinderUtil.findEntityById(
        userRepository.findById(userId),
        User.class
    );

    Todo foundTodo = EntityFinderUtil.findEntityById(
        todoRepository.findById(todoId),
        Todo.class
    );

    boolean isInvalidUser = foundTodo.getUser() == null
        || !ObjectUtils.nullSafeEquals(
        foundUser.getId(),
        foundTodo.getUser().getId()
    );

    if (isInvalidUser) {
      throw new InvalidRequestException(
          "User who created todo is invalid"
      );
    }

    Manager foundManager = EntityFinderUtil.findEntityById(
        managerRepository.findById(managerId),
        Manager.class
    );

    boolean isManagerMismatch = !ObjectUtils.nullSafeEquals(
        foundTodo.getId(),
        foundManager.getTodo().getId()
    );

    if (isManagerMismatch) {
      throw new InvalidRequestException(
          "Manager is not assigned to todo"
      );
    }

    managerRepository.delete(foundManager);
  }
}

 

 

User foundUser = EntityFinderUtil.findEntityById(
    userRepository.findById(userId),
    User.class
);

 

User foundUser = EntityFinderUtil.findEntityById(
    userRepository,
    userId,
    User.class
);

 

 

3. [해결 완료]

 

3-1. [회고] 굳이 유틸로 빼야 할까? / 리스트나 페이징 추가도 

3-2. [전후 데이터 비교]