Skip to content

Latest commit

 

History

History
316 lines (273 loc) · 11.5 KB

8주차_멘토링.md

File metadata and controls

316 lines (273 loc) · 11.5 KB

1. 베스트 게시글 조건식을 어떻게 세우면 좋을지 궁금합니다.

  • 현재 베스트 게시글 조건식은 간단히 좋아요 숫자로만 정렬해서 반환 (1번 조건식)

조건

1. 기간 x 일
2. 좋아요 수 y 개 
3. 댓글 수 z 개

조건식

1. y -> (현재)
2. y + z * (상수값)
3. x 이내, y + z * (상수값)

멘토님 피드백

  • x: 1일 / 7일 / 30일
  • z(댓글 수) * 가중치(0.3 ~ 0.5)
  • y, z 최소 기준값

2. 진행도 타입 A,B,C,D → 인터페이스 vs 전략클래스

비즈니스 요구 사항

  • 보증금 피해 사례는 4가지의 타입별로 분류

  • 타입별로 진행해야하는 메인단계가 다릅니다.

  • 타입별로 메인 단계가 중복될 수 있고, 메인단계는 여러개의 서브 단계로 저장됩니다.

    A 타입

    • 임차권 등기 명령 > - 세부 단계 … (→ 완료 여부를 저장해야함 )
    • 보증 이행 청구

    B 타입

    • 보증 이행 청구
  • 각 메인 단계의 서브 단계의완료 여부를 저장해야합니다.

방법 1 ) 인터페이스

  • 진행도 상위 클래스를 두고 각 타입이 진행도 클래스를 상속받는 식

ERD

image.png

방법 2 ) 전략 객체

  • 모든 타입이 함께 쓰는 범용 진행도 엔티티를 두고, 진행도의 타입에 따라 필요한 동작을 전략 객체가 결정
  • 서비스 → 전략 객체 → 엔티티
  • 전략 객체 사용이유 : 타입마다 중복되는 메인 단계가 존재해 한 테이블에 두는게 더 단순하게 흐름을 가져갈 것으로 예상, 이후 타입별로 로직을 서비스 계층에 두다보니 로직이 복잡해져 전략 객체를 사용

kakao-tech-campus-2nd-step3#52

예시)

  • 타입별 메인 단계를 가져와야할 때
    • 서비스 계층

        ```java
        //클래스까지
            progress.getActiveMainStepList()
        ```
      
    • 엔티티

      // 
      public List<MainStep> getActiveMainStepList() {
          return ProgressStrategy.generateActiveMainStepsByType(this);
      }
    • 전략 객체

          public static List<MainStep> generateActiveMainStepsByType(Progress progress) {
              ProgressType progressType = progress.getProgressType();
        
              if (progressType.equals(ProgressType.A)) {
                  return completeMainStepListByTypeA(progress);
              }
              ...타입에 따라서 
              return completeMainStepListByTypeD(progress);
        
          }    
            
          private static List<MainStep> completeMainStepListByTypeA(Progress progress) {
              List<MainStep> mainSteps = new ArrayList<>();
              mainSteps.add(progress.getMainStepEx());
              return mainSteps;
          }
            

멘토님 피드백

  • 방법 1: 인터페이스+상속
    • 구현하면 타입 안정성은 좋다
    • 나중에 새로운 기능 추가, 수정, 리팩터링하기가 어렵다
      • 부모 클래스의 변경 사항이 그대로 하위 클래스에 반영되면서 코드 수정이 어렵다
    • 중간에 부모 클래스를 더 둘 수도 있지만 계층이 깊어지면 복잡도가 증가한다
  • 방법 2: 전략 패턴
    • 어떤 도메인에 타입 여러 개가 존재하는 경우, 공통 로직이 있는 경우 많이 씀
    • 공통 로직 처리
      • 모든 타입에 공통 → Progress 포함 (O)
      • 4개 중 2개만 공통 → ??
        • 어디까지 포함 시킬건지 고민 필요
      • 공통 로직을 별도의 전략 클래스로 (A전략 클래스가 Z전략 클래스를 조합)
    • → 전략 패턴 + 조합(composition) 추천

3. 반환값 : 이메일만 vs PrincipalDetails

컨트롤러에서 서비스 계층으로 사용자 인증 정보를 전달할 때 가장 권장되는 방식에 대해 고민을 가지고 있습니다. 현재 고려하고 있는 두 가지 방식은 다음과 같습니다:

  1. PrincipalDetails 객체를 서비스 계층에 직접 전달하는 방식
@GetMapping("/{board_id}/likes")
public ResponseEntity<LikeSummaryResponseDto> getLikesByBoardId(
    @AuthenticationPrincipal PrincipalDetails principalDetails,
    @PathVariable(name = "board_id") Long boardId) {
    LikeSummaryResponseDto summary = likeService.getLikeSummary(principalDetails, boardId);
    return ResponseEntity.ok(summary);
}4
  1. 컨트롤러에서 필요한 정보(email)만 추출하여 전달하는 방식
@PutMapping("/board/comments/{comment_id}")
public ResponseEntity<Void> updateComment(
    @AuthenticationPrincipal PrincipalDetails principalDetails,
    @PathVariable("comment_id") Long commentId,
    @Valid @RequestBody CommentRequestDto commentRequestDto) {
    commentService.updateComment(principalDetails.getMemberEmail(), commentId, commentRequestDto);
    return ResponseEntity.status(HttpStatus.OK).build();
}

각 방식의 장단점과 실무에서 선호되는 방식에 대해 조언해 주시면 감사하겠습니다.

  • 상황에 따라 유동적으로 선택해도 될까요? 아니면 통일하는게 권장될까요?

  • ex) 로그인하지 않아도 좋아요 조회 가능하도록 서비스 계층에서 구현 시 principalDetail을 넘겨야 함.

    @Transactional(readOnly = true)
        public LikeSummaryResponseDto getLikeSummary(PrincipalDetails principalDetails, Long boardId) {
            Board board = boardService.getBoard(boardId);
            int likeCount = likeRepository.countByBoardIdAndIsDeletedFalse(board.getId());
    
            boolean isLiked = false;
            if (principalDetails != null) {
                Member loginMember = memberService.getMemberByEmail(principalDetails.getMemberEmail());
                isLiked = likeRepository.existsByMemberAndBoardAndIsDeletedFalse(loginMember, board);
            }
    
            return new LikeSummaryResponseDto(likeCount, isLiked);
        }

멘토님 피드백

  • 실무
    • PrincipalDetails 전체 전달 X
      • 보안/인증 관련된 api, PrincipalDetails 필드를 필요로 한다면 PrincipalDetails를 전체로 전달
    • email이나 별도의 UserAuth 클래스(email, id, 이름,…)
      • 일반적인 api
  • 로그인 여부는 서비스가 아니라 컨트롤러 레이어 (필터, 인터셉터 등) 에서 이미 이루어지기 때문에 컨트롤러 코드에서 if (login) 체크하는 것이 어색하지 않음
  • 그러나 프로젝트 실무자들의 합의가 필요한 부분

4.응답시 도메인을 어디서 까지

컨트롤러에서 응답값을 반환할 때 페이지도메인을 반환해도 괜찮을지 아니면 DTO는 서비스 단까지만 나와야만 하는지 궁금합니다.

  1. Service에서 Domain 객체인 Board가 Controller 까지 반환하는 방식
    @GetMapping("/{board_id}")
    public ResponseEntity<BoardResponseDto> getBoardById(@PathVariable(name = "board_id") Long boardId) {
        Board board = boardService.getBoard(boardId);
    
    return ResponseEntity.status(HttpStatus.OK)
            .body(new BoardResponseDto(board));
    }
  • 장점 : 다른 Service 나 Controller 에서 함수를 호출할 때, 편리하게 사용할 수 있다
  • 단점 : Domain 객체가 Controller 까지 노출된다
  1. Service에서 처음부터 BoardDto를 반환하는 방식
    @GetMapping("/{board_id}")
    public ResponseEntity<BoardResponseDto> getBoardById(@PathVariable(name = "board_id") Long boardId) {
      BoardResponseDto boardResponseDto = boardService.getBoard(boardId);
    
      return ResponseEntity.status(HttpStatus.OK)
              .body(boardResponseDto);
    }
  • 장점 : Domain 객체가 Controller 에 노출되지 않는다
  • 단점 : 다른 Service 나 Controller 에서 함수를 호출할 때, Repository 를 호출하고, orElseThrow 와 같은 부가적인 작업을 해야할 수도 있다

멘토님 피드백

  • 이 부분도 프로젝트 실무자들의 합의가 필요한 부분
  • 개인적인 취향으로는 방법 1을 선호
    • A서비스 ↔ B서비스.. dto보다는 도메인 객체를 주고 받는게 호환성이 좋다

5. 페이지 객체 반환

페이지네이션시 반환하는 응답 객체형식에 대한 고민이 있습니다. 실무에서 선호되는 방식에 대해 조언해 주시면 감사하겠습니다.

  1. 현재 페이지 번호, 전체 페이지 번호, 객체 리스트 제공
{
  "total_page": 1,
  "current_page": 0,
  "item_list": [
    {
      "id": 3,
      "title": "상당히 졸리네요",
      "content": "안녕하세요.사진은제가어제,안녕하세요....",
      .... 
    },
    ... 
  ]
}
  1. 페이지 객체 그대로 반환
{
   "content": [
    {
        "id": 3,
        "title": "상당히 졸리네요",
        "content": "안녕하세요.사진은제가어제,안녕하세요....",
        ....
   },
   ...
   ],
   "pageable": {
    "pageNumber": 0,
    "pageSize": 10,
    "sort": {
        "empty": false,
        "sorted": true,
        "unsorted": false
   },
   "offset": 0,
   "paged": true,
   "unpaged": false
   },
   "last": true,
   "totalPages": 1,
   "totalElements": 1,
   "size": 10,
   "number": 0,  //현재 번호
   "sort": {
        "empty": false,
        "sorted": true,
        "unsorted": false
   },
   "first": true,
   "numberOfElements": 1,
   "empty": false
   }

멘토님 피드백

  • Page 객체를 그대로 응답에 주진 않는다
public class PageResponse<T> {
   private List<T> items; // data, content 이름..
   private int currentPage; // 현재 페이지 번호
   private int totalPages; // 전체 페이지 번호
   private int totalElements; // 전체 데이터 개수 (검색하면 n건)
   private boolean hasNext; // 다음 페이지 존재 여부
}

// 무한 스크롤
public class SliceReponse<T> {
   private List<T> items; // data, content 이름..
   private boolean hasNext; // 다음 페이지 존재 여부
   private int currentPage; 
}


[ 추가 TIP ✨ ]**

  • 보드 목록 조회
    • 댓글, 좋아요 수를 보드 엔티티에 포함 → 캐싱하고 대략적인 수치로 보여주는 방법
  • 조회 성능을 위해서 데이터를 중복 저장하는 경우도 있다
  • 좋아요 수는 변경이 많을 것 같은데 → 보드에 변경이 전파
  1. 보드가 변경이 많을 필요가 없을 것 같다
  2. 따닥 동시성 문제
  • 보드 쿼리 1회 + boardId로 count 쿼리, 2회

  • 멘토로 신청 시 이메일 인증

    • 이메일 인증 - 전용 메일 주소 기반으로 인증 ?
    • 메일 전송
  • 채팅

    • 실시간 웹 채팅
    • 쪽지
    • 라이브러리, 프로토콜… 고려 해보시고 시간, 인적 리소스가 된다면
  • 댓글/게시글 필터링

    • 욕설, 스팸, 개인정보… 하드코딩해서 필터링할수도 있음
    • 금방 하실 것 같음
  • 댓글/게시글 신고

    • 금방 하실 것 같음
    • 티가 잘 안날듯 (버튼 하나..)
  • 알림

    • 내가 쓴 글에 누군가 댓글 단 경우
    • 좋아요 누른 경우 (1건 단위, 5건, 10건 단위로)
    • 채팅) ‘OOO님이 채팅 신청을 하셨습니다’
    • 이메일 인증) ‘멘토 신청이 정상적으로 완료되었습니다’
    • 아티클 발행) 서비스에 적합한 글 구독…. 외부 글, 서비스 내에서 작성