티스토리 뷰

현재 진행 중인 부트캠프에서 최종프로젝트를 진행 중인데 벌써 절반의 시간이 지나갔다.

5주간의 최종프로젝트에서 벌써 3주차의 중간발표를 하게 되었다.

 

1. 시연영상

https://www.youtube.com/watch?v=DbSI2MoX6Z0&t=8s 

 

2. 기술적 의사결정 & 트러블슈팅 기록

  • Redis
    • 데이터 액세스 지연 시간을 줄일 수 있다.
    • 가용성이 뛰어난 인 메모리 캐시 구현에 매우 적합함.
    • 메모리를 저장하고 조회하는 것에 빠르다.
  • 크롤링
    • 로켓펀치 사이트 채용정보를 크롤링하기 위해서 Jsoup과 Selenium을 사용하였습니다.
    • Jsoup은 자바 오픈소스 라이브러리로 정적 페이지 크롤러입니다.
    • Selenium은 동적 크롤링을 지원해 줍니다.
    • Jsoup이 Selenium보다 처리속도가 빠르기 때문에 Jsoup으로 가능한 크롤링은 Jsoup으로 진행하였습니다.
  • QueryDSL
    • 동적 쿼리를 사용하기 위해 사용했습니다.
    • 컴파일 시점에 에러를 잡을 수 있도록 선택했습니다.

 

 

3. 중간발표 후 회고

1) 미구현된 MVP 기능

  • 댓글 수정 기능 구현중
  • 회원 탈퇴
  • 로그아웃

2) 추가/개선할 기능과 그 이유

  • (추가) 대댓글 기능 : 일반적인 타 커뮤니티처럼 대댓글을 이용할 수 있도록 하기 위해
  • (추가) 채팅 기능 : 커뮤니티 유저들 간의 실시간 소통을 위해
  • (추가) 이메일 인증 기능 : 메일 주소의 실존 여부와 실제 해당 메일 계정의 소유 여부를 검증하기 위해
  • (추가) 백그라운드 잡을 이용한 최신 자료 자동 크롤링 : 최신 자료를 하루 1번 내부서버에서 크롤링을 통해 최신화하기 위해서
  • Slack log 기록 연동 : 오류 발생 시 slack을 통해 즉각적으로 파악할 수 있음

3) 추가/개선할 기능을 어떻게 구현할 것인지

  • 대댓글 기능 → 대댓글을 작성할 때 모댓글의 id인 Cid값을 같이 넘겨주어 cGroup에 저장하고, cDepth는 1로 설정하여 누구의 대댓글인지 확인할 수 있게 설정하여 구현
  • 채팅 기능 → websocket(또는 socket.io) : 새로운 데이터가 들어오면 먼저 서버가 클라이언트에게 데이터를 전송하는 기술이므로 실시간 채팅 메시지를 주고받는 방식에 적합함
  • 이메일 인증 기능 → spring-boot-starter-mail을 사용해 이메일 인증 기능 구현
  • 백그라운드 잡을 이용한 최신 자료 자동 크롤링 → 스케줄러를 사용한 자동 크롤링 구현

 

4. 튜터님의 질문 - 다음 주까지 답변

 

1. 게시물 삭제라는 기능을 어드민용(deletePostAdmin), 사용자용(deletePost)으로 나누는 게 아니라, 하나의 메소드로 사용할 수 있는 방법은 없을까요?
더보기

- Admin의 기능과 사용자용 메서드를 나누는게 당연하다고 생각하고 진행하였다.

solid 원칙에서의 단일책임원칙을 지키기 위해서라고 생각하고 있었다.

사용자용 삭제애 서는 토큰에서 userDetails의 정보를 가져와서 본인의 정보와 일치하는지에 대한

검증을 한 번 더 진행함으로 써 관리자와는 처리속도의 차이가 있다고 생각한다.

하지만 이렇게 나누는 것보다 하나의 메서드로 정의하는 것이 옳은 것인지 궁금하다. 

// 유저 삭제 기능  (controller)
	@DeleteMapping("/{postId}")
    @ResponseStatus(HttpStatus.OK)
    @PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
    public void deletePost(@PathVariable Long postId,
        @AuthenticationPrincipal UserDetailsImpl userDetails) {

        postService.deletePost(postId, userDetails.getUser());
    }

// 유저 삭제 기능 (service)
    @Override
    @Transactional
    public void deletePost(Long postId, User user) {
        Post post = postRepository.findById(postId).orElseThrow(
            () -> new BadRequestException("삭제할 게시물이 존재하지 않습니다.")
        );

        post.validateUser(user);
        postCommentRepository.deleteByPostQuery(post);
        likeRepository.deleteByPostQuery(post);
        postRepository.delete(post);
    }
--------------------------------------------------------------------------------------
// 관리자 삭제 기능 (controller)
 	@DeleteMapping("/{postId}")
    @ResponseStatus(HttpStatus.ACCEPTED)
    public void deletePostAdmin(@PathVariable Long postId) {
        postService.deletePostAdmin(postId);
    }

// 관리자 삭제 기능 (service)
    @Override
    @Transactional
    public void deletePostAdmin(Long postId) {
        Post post = postRepository.findById(postId).orElseThrow(
            () -> new BadRequestException("삭제할 게시물이 존재하지 않습니다.")
        );

        postCommentRepository.deleteByPostQuery(post);
        likeRepository.deleteByPostQuery(post);
        postRepository.deleteById(postId);
    }

 

#튜터님의 코멘트

보통은 Admin과 User 테이블을 별도로 만든다.

Role이 더 많아지면 어떻게 할 것인가??

어드민/사용자일때에 인증의 차이이다. 인증을 컨트롤러에서 하면 deletePost를 사용할 수 있지 않을까?

PreAuthorize에서 검증을 할 수 있다. Post.validateUser를. 내가 post에 권한이 있는가?? → 이는 필터에서 검증을 마치고 온다.

parameter로 넘길때 많은것들을 전달하지 않는다. user를 통채로 넘기는 것이 아닌 userId만 넘겨주는 방식

 

2. 이미지 업로드 시, 클라이언트 -> 서버 -> S3, 이미지 다운로드 시 S3 -> 서버 -> 클라이언트의 구조인 경우, 클라이언트 <-> 서버 간 파일이 전송되면서 비용이 발생할 것 같은데. 비용을 적게 사용하면서, 이미지 업로드를 구현할 수 있는 방법이 있을까요? (+ 만약, S3가 아니라 FireStore를 이용하도록 변경해야 된다고 했을 때, 기존 코드 변경 없이, FireStore를 이용할 수 있는 방법이 있을까요?)
더보기

답변: 이미지 업로드 시 클라이언트에서 바로 S3에 업로드를 하고, S3의 url 정보만 서버에서 저장하는 방식으로 사용한다. (Presigned URL) 기존 코드 변경없이 FireStore를 사용할 수 있는 방법은 잘 모르겠습니다..

 

#튜터님의 코멘트

인터페이스를 사용하라. (카카오 로그인도 마찬가지)

NAS (Network Area Storage)

같은 행위를 하는것들에 대해 공통점을 찾아서 인터페이스화 하고 구현체를 다르게 만들면 된다.

  • keyword - 인터페이스 / 팩토리 패턴

기존 코드 변경 없이, 기능을 추가할 수 있어야한다.

Presigned URL → 비용절감을 위해. 파일이 두번 이동할 필요 없도록 하기 위해

왜 업로드와 다운로드의 통일성이 없었는가? 앞으로 설계를 할 때 더 나은 방법이 있는가 생각을 해보고 더 좋은 방향으로 고치도록 한다.

 

3. @Query에서 native=true를 사용하셨는데요. native=true를 사용하신 이유와 사용했을 때의 장단점을 설명하여 주세요.
더보기
    @Query(
        nativeQuery = true,
        value = "SELECT p.title AS title, p.id AS postId FROM post p WHERE p.user_id = :userId"
    )
    Page<PostsDto> findMyPagePosts(Pageable pageable, @Param("userId") Long userId);

 내가 네이티브 쿼리를 사용한 이유는 간단하다.

쿼리문을 처음 사용해 보았고, DB에서 원하는 값을 가져오기 위해서 구글링을 통해 방법을 찾던 중 JPA 쿼리문에 

대한 이해가 부족하여 SQL 쿼리문을 사용하였다.

프로젝트 진행에 급급한 나머지 다른 생각은 하지 못하였다.

이러한 질문을 받고 난 후 알아보았다. 

native query를 사용하는 이유는 주로 Update 쿼리를 날릴 때 사용한다고 한다.

JPA로 전체 update를 진행 시 for문을 통해 하나의 데이터를 수정하고 save를 진행하게 되어  N개의 데이터를 N번의 save를 하게 된다.

하지만 nativeQuery를 이용하면 1번의 save를 통해 update를 진행할 수 있다. 이러한 경우 데이터의 개수가 많아질수록 성능의 문제가 생겨 nativeQuery를 사용하는 것이 좋다. 이러한 경우가 아니라면 잘 사용하지 않는데,

DB에 따라 일부 코드에서 오작동이 생길 수 있다고 한다.

 

앞으로는 QueryDSL을 얼른 공부해서 사용해야겠다.

 

 

 

#튜터님의 코멘트

장점이 없다.

탈퇴는 비동기로 처리한다. 일단 업데이트로 처리한 후 진짜 삭제는 뒤에서 후처리로 처리한다.

항상 데이터가 많았을 때를 고민해라.

백엔드는 뭘 하든 사용자가 몰라야한다.

db를 안힘들게 하는 방법 : batch → 나눠서 처리하는것.

리얼 마이에스큐엘을 읽어라


남은 2주 동안 자신이 구현해보고 싶은 기능을 하나씩 추가하고, 배포 및 성능개선을 할 예정이다.

 

https://astrid-dm.tistory.com/496

https://escapefromcoding.tistory.com/442

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/11   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
글 보관함