티스토리 뷰
현재 진행 중인 부트캠프에서 최종프로젝트를 진행 중인데 벌써 절반의 시간이 지나갔다.
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
'개발 > Weekly I Learned' 카테고리의 다른 글
| WIL(Weekly I Learned)/23.01.15 (2) | 2023.01.16 |
|---|---|
| WIL(Weekly I Learned)/22.12.25 (0) | 2022.12.25 |
| WIL(Weekly I Learned)/22.12.18/ # JWT 회원가입구현 (0) | 2022.12.18 |
| WIL(Weekly I Learned)/22.12.11/ # Spring 게시판구현 (0) | 2022.12.09 |
| WIL(Weekly I Learned)/22.12.04/ # OOP 개념 정리 (0) | 2022.12.02 |