Spring 7기 프로젝트/뉴스피드 팀 프로젝트

[트러블슈팅] 프로젝트 개발 중 발생한 3가지 핵심 기술적 이슈에 대한 팀 차원의 결정사항

JuNo_12 2025. 5. 30. 16:11

회의 1: 게시물 수정/삭제 권한 제어 방식

검토된 방안들

  • 방안 1: Service Layer에서 직접 검증
  • 방안 2: Spring Security @PreAuthorize 활용
  • 방안 3: Repository Query 필터링

결정사항: 도메인별 유틸리티 클래스를 활용한 권한 검증

// PostAuthorizationUtil.java (게시물 전용)
@Component
public class PostAuthorizationUtil {
    
    public Post validateUserAccessToPost(Long postId, UserDetails userDetails, 
                                       PostRepository postRepository, 
                                       UserRepository userRepository) {
        User foundUser = userRepository.findByEmail(userDetails.getUsername())
            .orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다."));
        
        Post foundPost = postRepository.findById(postId)
            .orElseThrow(() -> new PostNotFoundException("게시물을 찾을 수 없습니다."));

        if(!foundUser.getId().equals(foundPost.getUser().getId())) {
            throw new UserMismatchException("내가 작성하지 않은 게시물은 수정할 수 없습니다.");
        }
        
        return foundPost;
    }
}

// CommentAuthorizationUtil.java (댓글 전용)
@Component
public class CommentAuthorizationUtil {
    
    public Comment validateUserAccessToComment(Long commentId, UserDetails userDetails,
                                             CommentRepository commentRepository,
                                             UserRepository userRepository) {
        User foundUser = userRepository.findByEmail(userDetails.getUsername())
            .orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다."));
        
        Comment foundComment = commentRepository.findById(commentId)
            .orElseThrow(() -> new CommentNotFoundException("댓글을 찾을 수 없습니다."));

        // 댓글 작성자 또는 게시물 작성자만 삭제 가능
        if (!foundComment.getUser().getId().equals(foundUser.getId()) 
            && !foundComment.getPost().getUser().getId().equals(foundUser.getId())) {
            throw new UserMismatchException("댓글 삭제 권한이 없습니다.");
        }
        
        return foundComment;
    }
}

결정 이유

  1. 도메인별 특화: 각 서비스마다 필요한 권한 검증 로직이 다름
    • 게시물: 작성자만 수정/삭제 가능
    • 댓글: 댓글 작성자 또는 게시물 작성자가 삭제 가능
  2. 구조적 동일성: 유틸리티 클래스 방식과 @PreAuthorize 방식이 구조적으로 거의 동일
  3. 팀 역량 고려: @PreAuthorize의 SpEL, AOP 복잡성 대비 팀 이해도 높음
  4. 점진적 개선: 기존 코드에서 단계적 리팩토링 가능

 


회의 2: 상위 데이터 삭제 시 하위 데이터 처리 방식

검토된 방안들

  • 방안 A: CASCADE 삭제 (자동 삭제)
  • 방안 B: Soft Delete (논리적 삭제)
  • 방안 C: 별도 테이블로 이동

결정사항: Soft Delete (논리적 삭제) 방식

// BaseEntity.java
@MappedSuperclass
public abstract class BaseEntity {
    @CreatedDate
    private LocalDateTime createdAt;
    
    @LastModifiedDate
    private LocalDateTime modifiedAt;
    
    private LocalDateTime deletedAt;        // 추가
    
    @Column(nullable = false)
    private Boolean deleted = false;        // 추가
}

추가 논의: 서비스 책임 분리

검토된 방안들:

  • 방안 1: 유저 서비스에서 관련 댓글과 게시물을 한번에 처리
  • 방안 2: 각 서비스별 책임 분리 + 상위에서 조합

 

최종 결정: 방안 2 (책임 분리 방식)

// UserService.java
@Transactional
public void deleteUser(Long userId) {
    // 1. 댓글 논리적 삭제
    commentService.deleteCommentsByUserId(userId);
    
    // 2. 게시물 논리적 삭제  
    postService.deletePostsByUserId(userId);
    
    // 3. 사용자 논리적 삭제
    userRepository.softDelete(userId);
}

결정 이유

  1. 데이터 복구 가능성: 사용자 실수나 시스템 오류 대응
  2. 책임 분리 원칙: 각 서비스가 자신의 도메인만 담당
  3. 확장성: 향후 복잡한 비즈니스 로직 추가 용이

 


회의 3: Service Layer와 인증 객체 의존성

검토된 방안들

  • 방안 A: 현재 방식 유지 (UserDetails 직접 사용)
  • 방안 B: CustomUserDetails 활용 개선
  • 방안 C: 인터셉터를 활용한 자동 주입
  • 방안 D: Controller에서 직접 추출

결정사항: 방안 B (CustomUserDetails 활용 개선)

// 기존 방식 (성능 이슈)
public PostResponseDto createPost(UserDetails userDetails, CreatePostRequestDto dto) {
    User user = userRepository.findByEmail(userDetails.getUsername())  // 매번 DB 조회
        .orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다."));
}

// 개선된 방식
public PostResponseDto createPost(CustomUserDetails userDetails, CreatePostRequestDto dto) {
    Long userId = userDetails.getUserId();  // DB 조회 없이 바로 사용
    User user = userRepository.findById(userId)...
}

결정 이유

  1. 성능 개선: 불필요한 email 기반 DB 조회 제거
  2. 점진적 개선: 기존 구조 최소 변경으로 개선 효과 획득
  3. 팀 부담 최소: UserDetails → CustomUserDetails 타입 변경만 필요
  4. 실용성: 이론적 완벽함보다 현실적 개발 효율성 우선

전체 결정사항 요약

구분 결정사항핵심  이유
권한 제어 도메인별 유틸리티 클래스 도메인 특화 + 팀 이해도
데이터 삭제 Soft Delete + 책임 분리 복구 가능성 + 확장성
인증 객체 CustomUserDetails 개선 성능 + 최소 변경

공통 결정 원칙

  • 팀 역량 고려: 복잡한 기술보다 팀이 이해하고 유지보수할 수 있는 방식 선택
  • 점진적 개선: 기존 코드 구조를 최대한 활용하면서 단계적 개선
  • 실용성 우선: 이론적 완벽함보다 현실적 개발 효율성과 일정 준수