- 현재 베스트 게시글 조건식은 간단히 좋아요 숫자로만 정렬해서 반환 (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 최소 기준값
-
보증금 피해 사례는 4가지의 타입별로 분류
-
타입별로 진행해야하는 메인단계가 다릅니다.
-
타입별로 메인 단계가 중복될 수 있고, 메인단계는 여러개의 서브 단계로 저장됩니다.
A 타입
- 임차권 등기 명령 > - 세부 단계 … (→ 완료 여부를 저장해야함 )
- 보증 이행 청구
B 타입
- 보증 이행 청구
-
각 메인 단계의 서브 단계의완료 여부를 저장해야합니다.
- 진행도 상위 클래스를 두고 각 타입이 진행도 클래스를 상속받는 식
ERD
- 모든 타입이 함께 쓰는 범용 진행도 엔티티를 두고, 진행도의 타입에 따라 필요한 동작을 전략 객체가 결정
- 서비스 → 전략 객체 → 엔티티
- 전략 객체 사용이유 : 타입마다 중복되는 메인 단계가 존재해 한 테이블에 두는게 더 단순하게 흐름을 가져갈 것으로 예상, 이후 타입별로 로직을 서비스 계층에 두다보니 로직이 복잡해져 전략 객체를 사용
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) 추천
컨트롤러에서 서비스 계층으로 사용자 인증 정보를 전달할 때 가장 권장되는 방식에 대해 고민을 가지고 있습니다. 현재 고려하고 있는 두 가지 방식은 다음과 같습니다:
- 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
- 컨트롤러에서 필요한 정보(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
- PrincipalDetails 전체 전달 X
- 로그인 여부는 서비스가 아니라 컨트롤러 레이어 (필터, 인터셉터 등) 에서 이미 이루어지기 때문에 컨트롤러 코드에서 if (login) 체크하는 것이 어색하지 않음
- 그러나 프로젝트 실무자들의 합의가 필요한 부분
컨트롤러에서 응답값을 반환할 때 페이지도메인을 반환해도 괜찮을지 아니면 DTO는 서비스 단까지만 나와야만 하는지 궁금합니다.
- 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 까지 노출된다
- 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보다는 도메인 객체를 주고 받는게 호환성이 좋다
페이지네이션시 반환하는 응답 객체형식에 대한 고민이 있습니다. 실무에서 선호되는 방식에 대해 조언해 주시면 감사하겠습니다.
- 현재 페이지 번호, 전체 페이지 번호, 객체 리스트 제공
{
"total_page": 1,
"current_page": 0,
"item_list": [
{
"id": 3,
"title": "상당히 졸리네요",
"content": "안녕하세요.사진은제가어제,안녕하세요....",
....
},
...
]
}
- 페이지 객체 그대로 반환
{
"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회 + boardId로 count 쿼리, 2회
-
멘토로 신청 시 이메일 인증
- 이메일 인증 - 전용 메일 주소 기반으로 인증 ?
- 메일 전송
-
채팅
- 실시간 웹 채팅
- 쪽지
- 라이브러리, 프로토콜… 고려 해보시고 시간, 인적 리소스가 된다면
-
댓글/게시글 필터링
- 욕설, 스팸, 개인정보… 하드코딩해서 필터링할수도 있음
- 금방 하실 것 같음
-
댓글/게시글 신고
- 금방 하실 것 같음
- 티가 잘 안날듯 (버튼 하나..)
-
알림
- 내가 쓴 글에 누군가 댓글 단 경우
- 좋아요 누른 경우 (1건 단위, 5건, 10건 단위로)
- 채팅) ‘OOO님이 채팅 신청을 하셨습니다’
- 이메일 인증) ‘멘토 신청이 정상적으로 완료되었습니다’
- 아티클 발행) 서비스에 적합한 글 구독…. 외부 글, 서비스 내에서 작성