🖤 UMC '커플' 앱 개발 프로젝트 - API 설계 (1) [유저] 문의 등록, 문의 세부 조회 🖤
2024. 1. 25. 00:30ㆍ개발/🖤 UMC 프로젝트 - 커플 🖤
이제 본격적인 서비스 로직 설계를 위해 API 제작을 시작했다
우선 아직 애매한 datespot 로직 부분을 제외하고 필요한 API 목록을 리스트업했다
👾 API 목록 👾
[문의하기 파트]
<유저>
나의 문의 리스트 조회 API 문의 등록 API
-> 문의 제목 / 내용
<운영자>
(page) 답변 대기 중인 문의 리스트 조회 API
문의 답변 등록 API
[계정 정보]
(page) 유저 정보 read API
유저 정보 update API
-> 프로필 사진 / 닉네임
회원 등록, 로그인 , 로그아웃 API
유저 리워드 read API
유저 point history read API
[후기]
(page) 프리뷰 리스트 조회 API -> pagable 고려? -> 내거, old , new , 추천수
후기 조회 API
후기 등록 및 수정 API
-> 사진 1~5장 / 제목 / 방문장소(1~3)
후기 후기 하트 API
나는 [ 문의하기 ] 파트를 맡았다!
🎀 [ 유저 ] 문의 등록하기
Qna Entity
@Entity
@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Qna extends BaseEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long qnaId;
private String title;
private String body;
@Enumerated(EnumType.STRING)
private AnswerStatus answerStatus = AnswerStatus.WAITING;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "memberId")
private Member member;
}
1. QnaRequestDTO Class 안에 QnaSaveDto static class 생성
public class QnaRequestDTO {
@Getter
public static class QnaSaveDto {
@NotNull
Long memberId;
@NotBlank
String title;
@NotBlank
String body;
}
}
✨ 코드 해석 및 추가 지식 ✨
1. Front에서 받아올 정보
- memberId -> '나의 문의 사항 조회하기' 기능 구현을 위해서 필요함
- title : 문의 제목
- body : 문의 내용
2. Front에서 보내주는 정보를 받아먹기만 하면 되기 때문에
@Getter만 필요하다
-> @Builder 와 같은 생성자 역할을 해주는 어노테이션은 필요 없다
3. 보통 DTO class 안에 관련 DTO를 public static class 형태로 넣어준다
📌 의문 1
QnaRequestDTO 내의 데이터들은 Front에서 알아서 넘겨주나?
따로 받아오는 로직 필요없나?
@RequestParam, @RequestBody, @PathVariable 등을 통해서 Json 혹은 변수 형식으로 데이터 통신
2. QnaConverter Class에 toQnaEntity 생성
@Component
@RequiredArgsConstructor
public class QnaConverter {
public static MemberService memberService;
public static Qna toQnaEntity(QnaRequestDTO.QnaSaveDto request){
Member member = memberService.findById(request.getMemberId());
return Qna.builder()
.member(member)
.title(request.getTitle())
.body(request.getBody())
.build();
}
}
✨ 코드 해석 및 추가 지식 ✨
1. Front한테 받은 정보를 저장한 DTO(QnaSaveDto)를 Qna Entity 인스턴스로 변환
- 우선 QnaSaveDto를 파라미터로 받아와야 한다
- Qna 인스턴스를 return한다
- Qna 인스턴스는 builder를 통해 생성한다
- 이때 memberId가 필요하지만 Qna의 builder 패턴을 통해서는 얻어올 수 없는 정보이기 때문에
MemberService의 findById 메서드를 이용해준다
2. @Component는 해당 클래스를 Spring bean으로 설정해주는 어노테이션이다
- Spring bean : Spring에서 관리하는 객체
📌 의문 2
컴포넌트인 MemberService를 불러와서 사용하기 때문에
QnaConverter를 컴포넌트로 설정하는 것이 좋다고 제안해주셨는데
정확한 의의가 뭐지?
참고 )
https://mangkyu.tistory.com/151
https://chat.openai.com/c/fdb50124-9107-469e-8e8f-ba470f1ab67d
public static class에 다른 로직(MemberService)이 끼어든 이상
유지보수의 의미가 옅어지기 때문에
스프링 빈으로 설정해 객체로 관리해주는 게 더 좋은 듯
3. QnaService class에 세부 로직 구현 - createQna class
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class QnaService {
private final QnaRepository qnaRepository;
private final QnaConverter qnaConverter;
@Transactional
public QnaResponseDTO.QnaEntityDto createQna(QnaRequestDTO.QnaSaveDto request){
Qna qna = qnaConverter.toQnaEntity(request);
qnaRepository.save(qna);
return qnaConverter.toQnaEntityDto(qna);
}
}
public interface QnaRepository extends JpaRepository<Qna, Long> {}
✨ 코드 해석 및 추가 지식 ✨
1. createQna : Qna 인스턴스 생성하고 QnaRepository에 저장
- Front에서 보내주는 정보를 가지고 Qna 인스턴스 생성해주기
-> QnaConverter.toQnaEntity 에서 처리!
-> 파라미터로 QnaSaveDto 필요
- 생성된 Qna 인스턴스 QnaRepository에 저장
-> QnaRepository 필요 : JpaRepository 상속받은 QnaRepository 생성하기
-> QnaRepository에 save를 통해 Qna 인스턴스 저장
2. @Transactional
- 해당 메서드를 실행 중일 때 다른 연산이 끼어들지 않도록 한다
- 연산 과정에서 오류가 발생했을 때 변경 사항을 커밋하지 않는다
- readOnly = true
-> 해당 Entity를 조회용으로 읽어오기 때문에 중간에 변경사항이 발생했을 때 적용하지 않는다
-> 메모리 절약
-> 코드 가독성
참고 ) https://kafcamus.tistory.com/30
https://hungseong.tistory.com/74
3. @JpaRepository : JPA를 사용하여 데이터베이스를 조작하기 위한 메서드를 제공한다
-> CRUD 메서드, List CRUD 메서드, Query Creation 메서드, 영속성 컨텍스트 관련 메서드 사용 가능
참고 ) https://velog.io/@minju0426/JPARepository%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90%EC%82%AC%EC%9A%A9%EB%B2%95-Method
4. QnaController Class 구현
@RestController
@RequiredArgsConstructor
@RequestMapping("/qnas")
public class QnaController {
private final QnaService qnaService;
@PostMapping("")
public ApiResponse<Boolean> join(@RequestBody @Valid QnaRequestDTO.QnaSaveDto request){
Qna qna = qnaService.createQna(request);
return True;
}
}
✨ 코드 해석 및 추가 지식 ✨
1. 문의 '등록'이기 때문에 POST -> @PostMapping
2. join : Front로부터 QnaSaveDto 내의 데이터 전달받고 Qna 인스턴스 생성 및 저장 진행
-> Front로부터 QnaSaveDto 내의 데이터 전달받기 : @RequestBody @Valid
-> Qna 인스턴스 생성 및 저장 진행 : createQna 사용
3. @{}Mapping : HTTP 연산을 적용해주는 어노테이션 -> {} : GET, POST, DELETE, PUT 이 들어감
4. @RequestBody : Front에서 보내주는 Json 텍스트를 받아올 수 있도록 해주는 어노테이션
5. @Valid : @RequestBody로 받아온 정보에 대한 검증 수행해주는 어노테이션
6. 추후에 ResponseDTO 생성해서 return 해줘야 한다
🎀 [ 유저 ] 문의 세부 조회하기
1. QnaResponseDTO class에 QnaEntityDTO 생성
public class QnaResponseDTO {
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class QnaEntityDto {
Long qnaId;
Long memberId;
String title;
String body;
AnswerStatus answerStatus;
}
}
✨ 코드 해석 및 추가 지식 ✨
1. Front에서 받은 정보 ( memberId, title, body ) 에 qnaId와 AnswerStatus 추가하여 DTO 생성
- qnaId : PK
- AnswerStatus : 운영자가 답변했는지 여부 -> WAITING( 답변 대기중 ), COMPLETED( 답변 완료 )
-> Enum으로 생성함
📌 의문 3
QnaEntityDto는 운영자에게만 필요한가?
📌 의문 4
운영자에게만 필요하다면 memberId를 넣을 필요가 있는가?
2. QnaConverter class에 toQnaEntityDTO 생성
public static QnaResponseDTO.QnaEntityDto toQnaEntityDto(Qna qna){
AnswerStatus answerStatus = AnswerStatus.WAITING;
return QnaResponseDTO.QnaEntityDto.builder()
.qnaId(qna.getQnaId())
.memberId(qna.getMember().getMemberId())
.title(qna.getTitle())
.body(qna.getBody())
.answerStatus(answerStatus)
.build();
}
✨ 코드 해석 및 추가 지식 ✨
1. Qna 인스턴스(Entity)에 저장되어 있는 정보에 answerStatus 추가해서 DTO 생성
-> Qna 인스턴스에 저장되어 있는 정보 가져와야 하기 때문에 파라미터로 Qna 인스턴스 받아온다
-> QnaEntityDto를 builder 패턴으로 생성한다
-> Qna 인스턴스에 저장되어 있는 정보는 getter를 통해서 불러온다
-> AnswerStatus는 WAITING으로 설정한 뒤 DTO에 넣어준다
-> 생성된 DTO 반환해준다
2. Qna 인스턴스는 QnaRepository에서 불러온다 -> QnaService 단에서 구현
📌 의문 5
QnaEntityDto에는 memberId가 있는데 toQnaEntityDto에 memberId 관련 로직이 없어도 되는가?
놉 그래서 에러 뜸...;; -> memberId 관련 로직 추가
📌 의문 6
qnaId는 자동으로 생성되는 것인가?
-> MySQL에서 자동으로 생성해준다
3. QnaService에서 구체적인 로직 구현 - findById, getQnaInfo
findById
public Qna findById(Long qnaId){
return qnaRepository.findById(qnaId).orElseThrow(()->new GeneralException(ErrorStatus.QNA_NOT_FOUND));
}
getQnaInfo
public QnaResponseDTO.QnaEntityDto getQnaInfo(Long qnaId) {
Qna qna = findById(qnaId);
return toQnaEntityDto(qna);
}
✨ 코드 해석 및 추가 지식 ✨
1. findById : QnaRepository에 저장되어 있는 Qna 인스턴스를 불러오는 로직
-> qnaId를 파라미터로 받아서 QnaRepository 안에 저장된 특정 Qna 인스턴스를 찾아온 뒤 반환한다
2. getQnaInfo : findById를 통해 반환 받은 Qna 인스턴스를 DTO로 변환하는 로직
-> findById 를 통해 반환받은 Qna 인스턴스를 qna 변수로 저장한다
-> Qna 인스턴스(Entity)를 DTO로 변환 : toQnaEntityDto 사용
📌 의문 7
findById에서 예외 처리를 안 하니까 Error가 떴는데 이유가 뭘까?
다른 메서드들은 예외 처리 안 해도 Error 안 떴는뎁..
-> 반환값이 null일 경우를 대비해서 실무에서는 보통 orElseThrow()를 이용해서 예외처리를 한다고 한다
4. QnaController에서 GET 로직 구현
@PostMapping("")
public ApiResponse<QnaResponseDTO.QnaEntityDto> join(@RequestBody @Valid QnaRequestDTO.QnaSaveDto request){
QnaResponseDTO.QnaEntityDto qnaDTO = qnaService.createQna(request);
return ApiResponse.onSuccess(qnaDTO);
}
우선 Qna관련 ResponseDTO을 구현했으므로
join( POST - 문의 등록하기 ) 메서드의 return 값을
QnaEntityDto(Qna관련 ResponseDTO) 로 설정해준다
@GetMapping("")
public ApiResponse<QnaResponseDTO.QnaEntityDto> getQna(@RequestParam Long qnaId){
return ApiResponse.onSuccess(qnaService.getQnaInfo(qnaId));
}
✨ 코드 해석 및 추가 지식 ✨
1. 세부 문의 '조회'하기 기능이므로 @GetMapping을 사용한다
2. 문의 정보를 return해준다 -> getQnaInfo 이용
3. @RequestParam : HTTP 요청 파라미터를 받을 수 있도록 하는 어노테이션
💡 API 구현은 [ DTO 생성 -> Converter 구현 -> Controller 슬쩍 해보면서 Service에 어떤 로직 필요한지 확인 -> Service 구현 ( Repository도 이 과정에서 만드는 듯 ) -> Controller 완성 ] 이런 순서로 보통 진행하는 것 같다 💡
'개발 > 🖤 UMC 프로젝트 - 커플 🖤' 카테고리의 다른 글
🖤 UMC '커플' 앱 개발 프로젝트 - API 설계 (2) [유저] 내 문의 preview 조회 🖤 (1) | 2024.01.31 |
---|---|
🖤 UMC '커플' 앱 개발 프로젝트 - ERD 설계 🖤 (1) | 2024.01.04 |
🖤 UMC 방학 앱 개발 프로젝트 시작 🖤 (2) | 2024.01.03 |