Skip to content
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

[FEAT] 주제, 선택지 조회 기능 구현 #36

Merged
merged 23 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
009f49d
feat: RoomQuestion에 생성 일자 추가 #28
leegwichan Jul 19, 2024
5d970fb
feat: RoomQuestion, BalanceOption의 Repository 구현 #28
leegwichan Jul 19, 2024
c4ae03e
feat: 방 질문 조회 서버스 계층 구현 #28
leegwichan Jul 19, 2024
c1867d3
chore: 컨트롤러 테스트를 위해 Rest-Assured 의존성 추가 #28
leegwichan Jul 19, 2024
81b9d14
feat: 방 질문 조회 API 구현 #28
leegwichan Jul 19, 2024
62a14a9
build: rest-assured을 최신 버전(5.5.0)으로 변경 #28
leegwichan Jul 20, 2024
0a4501b
test: 테스트 케이스 일부 변경 및 추가 #28
leegwichan Jul 20, 2024
da4ed13
test: 일부 어노테이션의 기본값 명시한 것 제거, ServiceTest에서 Mock 환경 사용 #28
leegwichan Jul 20, 2024
f57c30c
style: test 파일 개행 및 들여쓰기 맞춤 #28
leegwichan Jul 20, 2024
8566753
refactor: 용도를 나타내기 위해 AuditingEntity 에서 BaseEntity로 파일 이름 변경 #28
leegwichan Jul 20, 2024
935ff1e
refactor: Repository 를 domain 패키지로 이동 및 필요없는 에노테이션 제거 #28
leegwichan Jul 20, 2024
458cd54
refactor: 지연 로딩의 여파를 막기 위해, 엔티티의 @EqualsAndHashCode, @ToString 제거 #28
leegwichan Jul 20, 2024
de80d87
fix: 서버 문제 트래킹을 명확히 하기 위해, ViolateDataException을 error 수준으로 로깅함 #28
leegwichan Jul 20, 2024
31e3543
refactor: 예외를 아키텍쳐 단위의 패키지로 이동 #28
leegwichan Jul 20, 2024
06b3dba
refactor: 구분을 쉽게 하기 위해 일부 테이블 및 컬럼 이름 변경 #28
leegwichan Jul 20, 2024
9e061d6
fix: RoomContentRepository를 room 패키지로 변경 #28
leegwichan Jul 22, 2024
246538a
fix: 서버 에러 시, 에러 스택 트레이스까지 출력하도록 변경, 일부 `@ResponseStatus` 추가 #28
leegwichan Jul 22, 2024
b042859
refactor: balance 패키지 추가 #28
leegwichan Jul 22, 2024
3d5b963
fix: API 명세서를 반영하여 Response 수정 #28
leegwichan Jul 22, 2024
112e91b
feat: BalanceContentController roomId 유효성 검사 추가
leegwichan Jul 22, 2024
39691e1
test: 테스트를 명확하게 설명하기 위해, Nested class 이름 수정
leegwichan Jul 22, 2024
b7ba2f4
test: given, when, then 주석 추가 #28
leegwichan Jul 22, 2024
2cc2472
test: 테스트 클래스 변경 #28
leegwichan Jul 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testImplementation 'io.rest-assured:rest-assured:5.5.0'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ddangkong.controller.content;

import ddangkong.controller.content.dto.BalanceContentResponse;
import ddangkong.service.content.BalanceContentService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class BalanceContentController {

private final BalanceContentService balanceContentService;

@GetMapping("/balances/rooms/{roomId}/question")
public BalanceContentResponse getBalanceContent(@PathVariable Long roomId) {
return balanceContentService.findRecentBalanceContent(roomId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ddangkong.controller.content.dto;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의견) BalanceOption, BalanceContent 라는 도메인 네이밍이지만 패키지는 그냥 content 인 것이 괜찮을 지 고민이 되네요
저희가 밸런스 게임 제공 서비스가 아니라 대화주제 제공 서비스이며, 밸런스 게임은 그 중 하나의 도메인일 뿐이니 이런식으로 두는 건 어떨까에 대해 같이 고민해보면 좋을 것 같아요!
depth가 깊어지지만 알아보기는 더 쉬울 것 같기도 합니다:)

controller/
                    │   ├── balance/
                    │   │   ├── option/
                    │   │   │   └── BalanceOptionController.java
                    │   │   └── question/
                    │   │       └── BalanceQuestionController.java

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7/22 회의 결과, balance package 추가하도록 하겠습니다~


import ddangkong.controller.option.dto.BalanceOptionResponse;
import ddangkong.domain.content.BalanceContent;
import ddangkong.domain.content.Category;
import ddangkong.domain.option.BalanceOption;
import lombok.Builder;

public record BalanceContentResponse(
Long questionId,
Category category,
String title,
BalanceOptionResponse firstOption,
BalanceOptionResponse secondOption
) {

@Builder
private BalanceContentResponse(BalanceContent balanceContent,
BalanceOption firstOption,
BalanceOption secondOption) {
this(balanceContent.getId(),
balanceContent.getCategory(),
balanceContent.getName(),
BalanceOptionResponse.from(firstOption),
BalanceOptionResponse.from(secondOption));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ddangkong.controller.exception;

import ddangkong.service.excpetion.BusinessLogicException;
import ddangkong.service.excpetion.ViolateDataException;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
Expand All @@ -12,6 +14,8 @@
@Slf4j
public class GlobalExceptionHandler {

private static final String SERVER_ERROR_MESSAGE = "서버 오류가 발생했습니다. 관리자에게 문의하세요.";

@ExceptionHandler
public ErrorResponse handleBindingException(BindException e) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의견) 여기 ResponseStatus가 안되어 있는 것 같네요! 확인 부탁드려요!

log.warn(e.getMessage());
Expand All @@ -26,11 +30,27 @@ public ErrorResponse handleConstraintViolationException(ConstraintViolationExcep
return new ErrorResponse(e.getConstraintViolations());
}

@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleBusinessLogicException(BusinessLogicException e) {
log.warn(e.getMessage());

return new ErrorResponse(e.getMessage());
}

@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleViolateDataException(ViolateDataException e) {
log.error(e.getMessage());

return new ErrorResponse(SERVER_ERROR_MESSAGE);
}

@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleException(Exception e) {
log.error(e.getMessage());

return new ErrorResponse("서버 오류가 발생했습니다. 관리자에게 문의하세요.");
return new ErrorResponse(SERVER_ERROR_MESSAGE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ddangkong.controller.option.dto;

import ddangkong.domain.option.BalanceOption;

public record BalanceOptionResponse(
Long optionId,
String content
) {
public static BalanceOptionResponse from(BalanceOption balanceOption) {
return new BalanceOptionResponse(balanceOption.getId(), balanceOption.getName());
}
}
19 changes: 19 additions & 0 deletions backend/src/main/java/ddangkong/domain/BaseEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ddangkong.domain;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import java.time.LocalDateTime;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
public class BaseEntity {

@CreatedDate
@Column(updatable = false, nullable = false)
private LocalDateTime createdAt;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ddangkong.domain.question;
package ddangkong.domain.content;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand All @@ -8,17 +8,13 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EqualsAndHashCode
@ToString
public class BalanceQuestion {
public class BalanceContent {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand All @@ -29,5 +25,5 @@ public class BalanceQuestion {
private Category category;

@Column(nullable = false)
private String content;
private String name;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ddangkong.domain.question;
package ddangkong.domain.content;

public enum Category {
EXAMPLE,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ddangkong.domain.content;

import ddangkong.domain.room.RoomContent;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface RoomContentRepository extends JpaRepository<RoomContent, Long> {

Optional<RoomContent> findTopByRoomIdOrderByCreatedAtDesc(Long roomId);
}
4 changes: 0 additions & 4 deletions backend/src/main/java/ddangkong/domain/member/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,12 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EqualsAndHashCode
@ToString
public class Member {

@Id
Expand Down
12 changes: 4 additions & 8 deletions backend/src/main/java/ddangkong/domain/option/BalanceOption.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package ddangkong.domain.option;

import ddangkong.domain.question.BalanceQuestion;
import ddangkong.domain.content.BalanceContent;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
Expand All @@ -10,26 +10,22 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EqualsAndHashCode
@ToString
public class BalanceOption {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String content;
private String name;

@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "balance_question_id", nullable = false)
private BalanceQuestion balanceQuestion;
@JoinColumn(name = "balance_content_id", nullable = false)
private BalanceContent balanceContent;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ddangkong.domain.option;

import ddangkong.domain.content.BalanceContent;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BalanceOptionRepository extends JpaRepository<BalanceOption, Long> {

List<BalanceOption> findByBalanceContent(BalanceContent balanceContent);
}
4 changes: 0 additions & 4 deletions backend/src/main/java/ddangkong/domain/room/Room.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EqualsAndHashCode
@ToString
public class Room {

@Id
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ddangkong.domain.room;

import ddangkong.domain.question.BalanceQuestion;
import ddangkong.domain.BaseEntity;
import ddangkong.domain.content.BalanceContent;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
Expand All @@ -9,17 +10,13 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EqualsAndHashCode
@ToString
public class RoomQuestion {
public class RoomContent extends BaseEntity {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

질문) BaseEntity가 RoomContent에 필요한가에 대해 조금 생각해봤는데요!
현재 BaseEntity를 두는 이유는 생성일자를 보기 위함이고 그를 통해 질문 순서를 파악하기 위함으로 알고 있는데,
비즈니스 로직에서 날짜를 통해 비교하는 것보다는 id 값을 통해 비교하는 것이 더 편하고 빠르지 않나 싶었습니다.
혹시 이 부분에 대해서는 어떻게 생각하시나요?? 커찬뿐만 아니라 다들 궁금하네용

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ID의 역할과 더 떨어져있지 않나 싶습니다. ID의 역할은 해당 Row(객체)에 고유값을 부여하는 것이라고 생각합니다. 실 구현이 시간과 관련있다고 해서 이를 이용하여 구현한다면, 나중에 사이드 이펙트가 생길 수 있다고 생각합니다.

지금은 Auto increment 정책을 사용하고 있어서 가능하겠지만, 추후 이 정책이 바뀐다면 관련 구현이 바뀌어야 할 것 같아요,


@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand All @@ -30,6 +27,6 @@ public class RoomQuestion {
private Room room;

@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "balance_question_id")
private BalanceQuestion balanceQuestion;
@JoinColumn(name = "balance_content_id")
private BalanceContent balanceContent;
}
4 changes: 0 additions & 4 deletions backend/src/main/java/ddangkong/domain/vote/BalanceVote.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,12 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EqualsAndHashCode
@ToString
public class BalanceVote {

@Id
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package ddangkong.service.content;

import ddangkong.controller.content.dto.BalanceContentResponse;
import ddangkong.domain.option.BalanceOption;
import ddangkong.domain.option.BalanceOptionRepository;
import ddangkong.domain.content.BalanceContent;
import ddangkong.domain.content.RoomContentRepository;
import ddangkong.service.excpetion.BusinessLogicException;
import ddangkong.service.excpetion.ViolateDataException;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class BalanceContentService {

private static final int BALANCE_OPTION_SIZE = 2;

private final RoomContentRepository roomContentRepository;

private final BalanceOptionRepository balanceOptionRepository;

@Transactional(readOnly = true)
public BalanceContentResponse findRecentBalanceContent(Long roomId) {
BalanceContent balanceContent = findRecentContent(roomId);
List<BalanceOption> balanceOptions = findBalanceOptions(balanceContent);

return BalanceContentResponse.builder()
.balanceContent(balanceContent)
.firstOption(balanceOptions.get(0))
.secondOption(balanceOptions.get(1))
.build();
}

private BalanceContent findRecentContent(Long roomId) {
return roomContentRepository.findTopByRoomIdOrderByCreatedAtDesc(roomId)
.orElseThrow(() -> new BusinessLogicException("해당 방의 질문이 존재하지 않습니다."))
.getBalanceContent();
}

private List<BalanceOption> findBalanceOptions(BalanceContent balanceContent) {
List<BalanceOption> balanceOptions = balanceOptionRepository.findByBalanceContent(balanceContent);
validateBalanceOptions(balanceOptions);
return balanceOptions;
}

private void validateBalanceOptions(List<BalanceOption> balanceOptions) {
if (balanceOptions.size() != BALANCE_OPTION_SIZE) {
throw new ViolateDataException("밸런스 게임의 선택지가 %d개입니다".formatted(balanceOptions.size()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ddangkong.service.excpetion;

public class BusinessLogicException extends RuntimeException {

public BusinessLogicException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ddangkong.service.excpetion;

public class ViolateDataException extends RuntimeException {

public ViolateDataException(String message) {
super(message);
}
}
Loading