[CowAPI] 7-1. QnA 코드 리뷰

준돌·2022년 5월 31일
0

오늘의 Cow

목록 보기
10/45
post-thumbnail

1. [CowAPI] QnA 코드 리뷰


  • 게시글 하나에 작성하기에는 양이 많아지기 때문에 코드 리뷰를 분리했습니다.
  • 자세한 코드는 Github에서 확인하실 수 있습니다.

2. Slack과 연동


Slack

  • Slack SDK for Java를 참조했습니다.
  • QnA가 생성될 때, Slack으로 bot을 이용하여 메시지를 보냅니다.

properties

slack.token = {slack에서 제공하는 app 토큰} (ex, xoxp- ....)
slack.channel = {공지를 보낼 채널} (ex, #notice)

SlackService

@Service
public class SlackService {
    @Value(value = "${slack.token}")
    String token;

    @Value(value = "${slack.channel}")
    String channel;

    public void postSlackMessage(String message) {
        try {
            MethodsClient methods = Slack.getInstance().methods(token);
            ChatPostMessageRequest request = ChatPostMessageRequest.builder()
                    .channel(channel)
                    .text(message)
                    .build();

            methods.chatPostMessage(request);


        } catch (SlackApiException | IOException e) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
        }
    }
}
  • Service 계층에 등록합니다.
  • Slack SDK for Java를 참조하여 동기적으로 메시지를 보냅니다.

@RestController
public class QnAController {
    @PostMapping("/QnAs/QnA")
    public QnADto createQnA(@RequestHeader("Authorization") String userToken, @RequestBody QnADto qnADto) {
        slackService.postSlackMessage("새로운 QnA가 생성되었습니다.");
        return qnAService.createQnA(userToken, qnADto);
    }
	
    ...
    
}
  • QnA 생성 API에 알람 서비스를 추가합니다.

3. Code review


요구사항분석

  • QnA는 생성, 조회, 수정, 삭제가 가능합니다. (O)
  • 추가적으로 최신순, QnAId 순으로 정렬된 검색과 페이지네이션이 있습니다. (O)
  • QnA가 생성될 경우 Slack에 알림을 보냅니다. (O)

코드분석

😎 어노테이션에 대한 자세한 설명은 [CowAPI] User 코드 리뷰 에서 확인할 수 있습니다.

Controller

QnAController.java

@Api(tags = {"QnA 게시판"})
@RequestMapping(value = "/api/v1")
@RestController
@RequiredArgsConstructor
public class QnAController {
    private final QnAService qnAService;
    
	@PostMapping("/QnAs/QnA")
    public QnADto createQnA(@RequestHeader("Authorization") String userToken, @RequestBody QnADto qnADto) {
        return qnAService.createQnA(userToken, qnADto);
    }
    
	@GetMapping("/QnAs/{QnAId}")
    public QnADto readQnA(@RequestHeader("Authorization") String userToken, @PathVariable(value = "QnAId") Long qnAId) {
        return qnAService.readQnA(userToken, qnAId);
    }
    
	@PutMapping("/QnAs/QnA")
    public QnADto readQnA(@RequestHeader("Authorization") String userToken, @RequestBody QnADto qnADto) {
        return qnAService.updateQnA(userToken, qnADto);
    }
    
	@DeleteMapping("/QnAs/QnA")
    public QnADto deleteQnA(@RequestHeader("Authorization") String userToken, @RequestBody QnADto qnADto) {
        return qnAService.deleteQnA(userToken, qnADto);
    }
    
	@GetMapping("/QnAs/QnA/search")
    public QnAListDto searchQnA(@RequestParam(value = "query") String query) {
        return qnAService.searchQnA(query);
    }
    
	@GetMapping("/QnAs/QnA/page")
    public QnAListDto QnAPage(@RequestParam(value = "page") Long page) {
        return qnAService.pageQnA(page);
    }
}

😎 API 문서 : Swagger
😎 QnA 생성, 조회, 수정, 삭제는 Jwt 토큰을 받습니다.

Service

QnAService.java

@Service
@RequiredArgsConstructor
@Transactional
public class QnAService {
    private final QnARepository qnARepository;
    private final UserRepository userRepository;
    
    public QnADto createQnA(String userToken, QnADto qnADto) { ... }
    public QnADto readQnA(String userToken, Long qnAId) { ... }
    public QnADto updateQnA(String userToken, QnADto qnADto) { ... }
    public QnADto deleteQnA(String userToken, QnADto qnADto) { ... }
    public QnAListDto searchQnA(String query) { ... }
    public QnAListDto pageQnA(Long pageId) { ... }

😎 JWT 토큰 인증 및 인가 코드 수정하고 있습니다.
😎 위의 함수들은 각각의 기능에 따른 필요한 로직을 수행합니다.

Domain

Entity

@JsonIgnore

  • 메소드나 필드를 직렬화나 역직렬화 기능에서 무시할 것을 가리키는 어노테이션입니다.

😎 실제 DB의 User Table에는 QnAId가 존재하지 않고 Join을 통해서 가져옵니다.
😎 따라서 DB에서 가져올 때 역직렬화를 무시하고 클라이언트로 전송할때 직렬화를 무시하기 위해 사용합니다.

QnA.java

@Entity
@DynamicUpdate
public class QnA {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    ...
    
    @ManyToOne
    @JoinColumn(name = "User_email")
    private User user;
}

User.java

@Entity
@DynamicUpdate
public class User {

	...

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    @JsonIgnore
    List<QnA> QnAs = new ArrayList<>();
}

😎 Id를 Auto Increment로 지정합니다.
😎 User와 매핑합니다.

Repository

@Repository
public interface QnARepository extends JpaRepository<QnA, Long> {
    @Query(value =
            "Select * From QnA q Where (q.title Like %:query% Or q.content Like %:query%) " +
            "And q.isDeleted != TRUE " +
            "Limit :searchCnt", nativeQuery = true)
    List<QnA> searchByQuery(@Param("query") String query, @Param("searchCnt") Long searchCnt);

    @Query(value =
            "Select * From QnA q Where q.isDeleted != TRUE " +
            "Order by q.updatedDate DESC, q.id ASC " +
            "Limit :pageId, :pageCnt", nativeQuery = true)
    List<QnA> findByPage(@Param("pageId") Long pageId, @Param("pageCnt") Long pageCnt);

	...
    
}

😎 @Query를 통해 직접 SQL을 작성하여 검색과 페이지네이션 했습니다.
😎 JpaRepository에 PagingAndSortingRepository가 있음을 확인했습니다.
😎 리펙토링시 수정할 예정입니다.


Test Code

😎 User Test Code 와 동일한 구조로 리펙토링 예정입니다.

profile
눈 내리는 겨울이 좋아!

0개의 댓글