-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[3주차 세미나] 구현 과제 #13
base: main
Are you sure you want to change the base?
[3주차 세미나] 구현 과제 #13
Conversation
// 일기 작성 | ||
@PostMapping("/diary") | ||
public ResponseEntity<Void> createDiary( | ||
@RequestHeader("Authorization") final Long userId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RequestHeader
의 required 가 default 로 true 이므로,
해당 API 의 요청은 무조건 Authorization header 가 필요함. (없으면 별도의 예외를 발생할거임 <- 이건 찾아보시오)
- 따라서 값이 들어온다면 long 으로 보장이 될거야
그런데 일반적으로는 @RequestHeader
를 잘 사용하지 않는 편이야
왜냐하면
- Authorization 이라는 헤더의 value 가 String 이 들어오면 어떻게 처리할건지?
- ex. request 의 header 가 Authorization : "abc" 이렇게왔는데, Long 에 대한 타입 불일치가 발생해서
- String 이여서 NumberFormatException 날수도? 아니면 Spring level 에서 뭔가 파라미터 타입 불일치라는 예외를 발생시킬듯?
@RequestHeader
를 잘 사용하려면 이런 예외들 (required : true 일 때 헤더가 없는 경우, 헤더는 있으나 타입이 다를 경우) 다 대응을 해줘야한다
- ex. request 의 header 가 Authorization : "abc" 이렇게왔는데, Long 에 대한 타입 불일치가 발생해서
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일반적으로는
- ArgumentResolver 로 특정 어노테이션 (ex.
@AuthorizationHeader
)을 만들어서 값을 가져오도록- ArgumentResolver 내부의 로직에서 위에 말한 예외들에 대한 처리가능
- try { ~ } catch (Exception e) {~}
- ArgumentResolver 내부의 로직에서 위에 말한 예외들에 대한 처리가능
아니면 만약에 Spring Security 를 쓴다면 @AuthenticationPrincipal
이런거 쓰던지~
|
||
// 일기 작성 기능 | ||
@Transactional | ||
public Diary createDiary(final Long userId, DiaryCreateDto diaryCreateDto){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Long -> long
@Transactional(readOnly = true) | ||
public class DiaryService { | ||
|
||
private final UserService userService; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Service 간 호출
// 일기 작성 기능 | ||
@Transactional | ||
public Diary createDiary(final Long userId, DiaryCreateDto diaryCreateDto){ | ||
User user = userService.findById(userId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final User user = userService.findById(userId);
|
||
@Getter | ||
@RequiredArgsConstructor | ||
public class IllegalArgumentException extends RuntimeException{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네이밍을 고치면 좋을것같습니다
- IllegalArgumentException 이미 존재하는 Exception
- 얘는 어디서 발생할지 몰라
- java library 내부 코드를 따라가면 알수있긴한데,
@Getter | ||
@Table(name = "users") // user 는 예약어 이므로 users 로 명시적 지정 | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class User { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UserEntity 이런 네이밍이 좀 더 명확해보입니다
⭐ TODO
[필수]
[선택]
+++
[2주차 선택 과제 연장선]
초기 기획서 회의 결론
AND-SOPT-SERVER/forum#16
✅ Service 클래스의 역할 별 분리
DiaryService 클래스에서 역할 별로 분리한 4개의 클래스들을 필드로 가져와 이 클래스들을 통해 필요한 함수를 호출하도록 했습니다.
DiaryRemover
: DB에서 엔티티를 ‘삭제’하는 로직만 모아놓음DiaryRetriever
: DB에서 엔티티를 조회하여 ‘찾아오는’ 로직만 모아놓음DiarySaver
: DB에 엔티티를 ‘저장’하는 로직만 모아놓음DiaryUpdater
: DB의 엔티티를 ‘업데이트’ 하는 로직만 모아놓음⏩ 이를 통해 각 클래스별로 책임을 명확하게 분리했습니다.
⏩ 가독성 또한 향상시킬 수 있습니다.
✅ Entity
@Enumerated
EnumType에는 enum 이름 값을 DB에 저장하는
EnumType.STRING
과enum 순서(숫자)값을 DB에 저장하는
EnumType.ORDINAL
이 있지만,⇒ 안정성을 위해
@Enumerated(EnumType.STRING)
을 사용했습니다.cascade=CascadeType.REMOVE
cascade=CascadeType.ALL
로 지정할 경우, 불필요한 연산이 발생될 수 있으며, 가끔 User 삭제 시 관련된 Diary 가 함께 삭제되지 않는 문제가 발생되기도 합니다.(실제 앱잼에서의 경험담) 따라서REMOVE
로 지정해 주었습니다.@Setter 지양
엔티티에서의 setter 사용을 통한 외부에서의 위험한 변경을 막기 위해 불필요한 @Setter 어노테이션을 삭제했습니다. 그에 따라서 public 으로 선언되었던 필드들 모두 private 로 변경해 주었습니다. → 별도의 메서드를 선언해서 setter 를 대신하여 값을 변경해주도록 하자!
✅ 회원가입
@NotNull
vs.@NotBlank
@NotNull
@NotBlank
ResponseEntity.created
https://00h0.tistory.com/88
무의식적으로 사용하던 것에 대해 제대로 알고자 하여…
회원가입 후, ‘비밀번호’는 클라이언트에게 불필요한 정보라고 생각되어 반환하지 않았습니다.
✅ 일기 수정
User가 일기 수정 화면에 접속했더라도, 일기를 수정하지 않고 그대로 다시 <저장> 버튼을 누르는 상황이 있을 것이라 생각했습니다.
그래서
DiaryUpdateDto
의 모든 값이 null 일 때도 처리가 가능하도록 구현했습니다.DiaryUpdateDto
의 필드 값들을 검사 후, null 이면 기존 일기의 값을 유지하고, 수정이 발생하면 그 수정값을 적용하도록 구현했습니다.이 때,
DiaryUpdateDto
에서visible
변수는boolean
이 아니라Boolean
타입(Wrapper 타입)을 사용하여 null 값을 허용하도록 해주었습니다. → 사용자가visible
값을 수정하지 않을 경우에도 null 값을 가질 수 있도록 합니다.일기 수정 일일 2회 제한 기능 추가
Diary 엔티티 내부에 일일 수정 횟수를 검증하고 업데이트하는 로직을 두었습니다.
💖 페이징 처리(선택과제)
‘전체 일기 목록 조회’ 와 ‘내 일기 조회’ 의 로직은 비슷하므로
전체 일기 목록 조회
를 예시로 모든 구현 과정을 설명하도록 하겠습니다!✅ DiaryController _
getDiaryList()
카테고리, 정렬기준은 user 가 꼭 선택하지 않아도 되는 값이기 때문에 항목 선택에 따른 API를 굳이 분리하여 만들지 않고
required = false
로 Param 값을 처리할 수 있도록 했습니다.Category와 SoptOption Enum 클래스 안에
fromContent
메서드를 정의하였습니다. 이를 통해서 클라이언트 측에서 입력한 문자열 값을 해당 enum으로 변환하고, 파라미터 값에 제가 정의한 올바른 enum 상수의 content 값에 해당하는지 확인하는 역할을 하게끔 했습니다.잘못된 값이 들어왔을 경우에는, 서비스까지 가서 데이터를 처리할 필요가 없다고 판단하였기에, 컨트롤러 단에서 검증을 수행하도록 하였습니다.
Category
✅ Pageable
페이지 번호와 페이지 크기를 전달하면, 내부적으로 오프셋 기반 페이지네이션을 수행하는
Pageable
를 사용하여 페이징 처리를 구현했습니다.해당 서비스에서는 무한 스크롤과 같이 이전 커서의 위치가 중요한 것이 아니라, ‘페이지’에 집중하기 때문에 오프셋 기반 페이징을 사용하는 것이 적절한 것 같다고 판단했습니다.
case 별 분기문
<category 를 선택했을 경우, 하지 않았을 경우> 와 <sortOption을 선택했을 경우, 하지 않았을 경우> 를 나누어 2 x 2 = 4 가지 경우의 수를 만들었습니다.