[프로젝트] Trello

지인·2023년 8월 9일
0

프로젝트

목록 보기
17/17
post-custom-banner

구현

보드 관리 기능

  • 보드 초대
    - 특정 사용자들을 해당 보드에 초대시켜 협업을 할 수 있어야 합니다.
    - 보드 생성자만 초대 가능
    - 초대된 유저는 member 적용
    - 초대된 유저는 다시 초대 불가능

🐾 이메일 인증을 통한 초대를 구현하고 싶었는데 팀 프로젝트이고 일정이 일주일 밖에 되지 않아 아쉽지만 간단하게 구현하기로 했다.
보드 생성자만이 초대를 할 수 있고 초대된 유저는 member로 userBoardRepository에 저장된다.


추가 기능

  • 회원 권한 부여
    • MEMBER → ADMIN, ADMIN → MEMBER
    • 보드 생성자만 회원 권한 부여 가능


이슈

🔥 N+1

  • UserBoard 엔티티에서 ManyToOne 관계로 User 엔티티를 참조하고 있다.
    UserBoard 엔티티에서 userId를 사용해 사용자 ID에 해당하는 보드를 조회하려고 했다.
    이럴 경우, 각각의 보드마다 해당 보드의 작성자인 사용자 정보를 가져오기 위해 추가 쿼리를 실행하게 된다.
    이로 인해 사용자마다 보드 개수만큼 추가적인 쿼리가 실행되어 N+1 문제가 발생한다.
public interface UserBoardRepository extends JpaRepository<UserBoard, Long> {
    List<UserBoard> findAllByUserId(Long userId);
}

👏🏻 해결

  • Fetch join
    • 주어진 사용자 ID에 해당하는 모든 유저보드 엔티티를 가져오면서 작성자 정보를 함께 가져와서 추가적인 쿼리 없이 N+1 문제를 해결할 수 있다.
public interface UserBoardRepository extends JpaRepository<UserBoard, Long> {
    @Query("SELECT ub FROM UserBoard ub JOIN FETCH ub.board WHERE ub.user.id = :userId")
    List<UserBoard> findAllByUserIdWithUserFetch(Long userId);
}

🔥 AOP & 커스텀 어노테이션

  • [Spring] 커스텀 어노테이션

  • 처음에는 pointcut을 메서드 별로 사용했는데 너무 비효율적이라는 생각이 들었다.
    그리고 상황별로 나눠서 메서드를 묶어 처리하고 싶었다.

  • 찾아보니 커스텀 어노테이션을 통해서 메서드에 어노테이션을 추가해 일괄 처리할 수 있었다.

👏🏻 해결

  • AccessCheck 커스텀 어노테이션을 만들어 일괄 처리 하였다.
/*ExcludeLogging 어노테이션을 메서드에만 적용할 수 있도록 지정*/
@Target(ElementType.METHOD) 
/*ExcludeLogging 어노테이션이 런타임까지 유지되도록 지정*/
@Retention(RetentionPolicy.RUNTIME) 
public @interface AccessCheck {
}
@Pointcut("@annotation(com.example.tenrello.board.aop.AccessCheck)")
    private void accessCheckMethods() {
    }
    
    @Around("accessCheckMethods()")
    public Object boardAccessCheck(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();

            User user = (User) joinPoint.getArgs()[0];
            Long boardId = (Long) joinPoint.getArgs()[1];

            Board board = boardService.findByBoard(boardId);

            if (userBoardRepository.findByBoardIdAndUserId(boardId, user.getId()).isEmpty()) {
                log.warn("초대 받지 못한 유저입니다.");
                throw new BoardAccessException("초대 받지 못한 유저입니다.");
            }

        return joinPoint.proceed();
    }
profile
열쩡
post-custom-banner

0개의 댓글