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. [전후 데이터 비교]
'Troubleshooting: 무엇이 문제였는가? > Spring 심화 프로젝트' 카테고리의 다른 글
6단계: 예외 처리 메시지를 일일이 작성해야 할까? (수정 중) (0) | 2025.01.06 |
---|---|
6단계: 일정이 반복된다면? (수정 중) (0) | 2025.01.06 |
3단계: "아니, '/%08auth/sign-up'이라니. /%08, 넌 누구냐?" (수정 중) (0) | 2025.01.06 |