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

[2단계 - 웹 자동차 경주] 제이(이재윤) 미션 제출합니다. #103

Merged
merged 32 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3f32c55
feat: Level 1 코드 복구 (1단계 미션 때 삭제해서 복구했습니다.)
sosow0212 Apr 15, 2023
3e5a7b7
feat: 게임 플레이 이력 조회 API 구현
sosow0212 Apr 15, 2023
51ff720
refactor: 게임 플레이 이력 조회 API 메서드 분리 및 서비스는 비즈니스 로직만 담당하도록 변경
sosow0212 Apr 15, 2023
94431bc
docs: 기능 구현 목록 수정
sosow0212 Apr 15, 2023
9ddcf23
test: 서비스 레이어 테스트 코드 작성
sosow0212 Apr 15, 2023
fad9124
test: 게임 이력 조회 테스트 작성
sosow0212 Apr 15, 2023
f4ca361
refactor: 우승자를 구하는 기능을 도메인 메서드로 변경
sosow0212 Apr 15, 2023
290166e
refactor: 게임 저장 후 결과를 반환하도록 변경
sosow0212 Apr 15, 2023
7f42ead
fix: 자동차 이름을 List로 나누게 리팩토링 후 실패하는 테스트 수정
sosow0212 Apr 15, 2023
1c20cec
refactor: Cars 정적 팩토리 메서드를 도메인 내부에서 가능하도록 변경
sosow0212 Apr 15, 2023
7b6e1df
refactor: Console 자동차 경주 게임에서 View의 파라미터를 Dto로 변경
sosow0212 Apr 15, 2023
968e0e4
docs: 기능 구현 목록 수정
sosow0212 Apr 15, 2023
abb5763
style: 불필요한 개행 삭제
sosow0212 Apr 15, 2023
4366e3e
refactor: 메서드명 통일 및 final 키워드 통일
sosow0212 Apr 15, 2023
b8b9d97
refactor: for에서 forEach로 변경
sosow0212 Apr 15, 2023
197ad1a
refactor: ResponseEntity 사용할 때 생성자로 반환하는 방식에서 빌더 패턴으로 변경
sosow0212 Apr 18, 2023
54d2e80
refactor: 불필요한 메서드 제거
sosow0212 Apr 18, 2023
8ef2ef4
style: 테스트 데이터 셋을 더 보기 좋게 개행 수정
sosow0212 Apr 18, 2023
a0260f5
refactor: 모호한 메서드명 변경
sosow0212 Apr 18, 2023
5f7f343
refactor: 외부에서 호출 안 하는 메서드의 접근 제어자를 private로 변경
sosow0212 Apr 18, 2023
53fbec3
test: GameResultDao 테스트 작성
sosow0212 Apr 18, 2023
08e605f
refactor: 컨트롤러 테스트 반환 값도 나오도록 리팩토링 진행
sosow0212 Apr 18, 2023
beca51d
test: 서비스 통합테스트 작성
sosow0212 Apr 18, 2023
c2e063e
refactor: 메서드명과 @DisplayName 변경
sosow0212 Apr 18, 2023
39e5cd1
fix: 테스트 개별 실행시에는 성공하지만, 전체 실행시 실패하는 문제 해결 (profile 설정)
sosow0212 Apr 18, 2023
0dd122b
chore: validation 종속성 추가
sosow0212 Apr 19, 2023
8f340b9
style: 디렉토리 이동
sosow0212 Apr 19, 2023
583b3fb
refactor: 잘못된 입력 값에 대해 예외처리 추가
sosow0212 Apr 19, 2023
8c0736e
test: 사용자 입력에 대한 값 검증 테스트 작성
sosow0212 Apr 19, 2023
b7d055c
refactor: 내부에서만 호출할 수 있도록 메서드의 접근제어자 변경
sosow0212 Apr 19, 2023
a2125d5
refactor: 상태코드가 중복된 의미이므로, 반환시에 상태코드를 반환 안 하도록 변경
sosow0212 Apr 20, 2023
6782237
refactor: 테스트 경우 추가
sosow0212 Apr 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation'
runtimeOnly 'com.h2database:h2'
}

Expand Down
22 changes: 22 additions & 0 deletions src/main/java/racingcar/advice/ControllerAdvice.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package racingcar.advice;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import racingcar.dto.response.ErrorResponse;

import java.util.Objects;

@RestControllerAdvice
public class ControllerAdvice {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleException(final MethodArgumentNotValidException exception) {
String errorMessage = Objects.requireNonNull(exception.getBindingResult().getFieldError()).getDefaultMessage();

return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ErrorResponse.send(HttpStatus.BAD_REQUEST.value(), errorMessage));
begaonnuri marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package racingcar.controller;

import java.util.InputMismatchException;
import java.util.List;
import java.util.stream.Collectors;
import racingcar.domain.Cars;
import racingcar.domain.TryCount;
import racingcar.dto.CarStatusResponseDto;
import racingcar.dto.GameHistoriesResponseDto;
import racingcar.dto.car.CarStatusResponseDto;
import racingcar.dto.car.GameHistoriesResponseDto;
import racingcar.utils.RandomPowerGenerator;
import racingcar.view.InputView;
import racingcar.view.OutputView;

import java.util.InputMismatchException;
import java.util.List;
import java.util.stream.Collectors;

public class RacingGameConsoleController {

private static final String CAR_NAME_DELIMITER = ",";
Expand All @@ -33,7 +34,7 @@ public void startGame() {

startRace(cars, tryCount);

GameHistoriesResponseDto gameHistoryDto = GameHistoriesResponseDto.toDto(findWinnerNames(cars), findCarStatuses(cars));
GameHistoriesResponseDto gameHistoryDto = GameHistoriesResponseDto.toDto(getWinnerNames(cars), convertToCarStatuses(cars));

outputView.printWinners(gameHistoryDto);
outputView.printCurrentRacingStatus(gameHistoryDto);
Expand Down Expand Up @@ -67,11 +68,11 @@ private void startRace(final Cars cars, final TryCount tryCount) {
}
}

private String findWinnerNames(final Cars cars) {
private String getWinnerNames(final Cars cars) {
return String.join(CAR_NAME_DELIMITER, cars.getWinnerNames());
}

private List<CarStatusResponseDto> findCarStatuses(final Cars cars) {
private List<CarStatusResponseDto> convertToCarStatuses(final Cars cars) {
return cars.getCars().stream()
.map(CarStatusResponseDto::toDto)
.collect(Collectors.toList());
Expand Down
28 changes: 14 additions & 14 deletions src/main/java/racingcar/controller/RacingGameWebController.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
package racingcar.controller;

import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import racingcar.domain.Cars;
import racingcar.domain.TryCount;
import racingcar.dto.GameHistoriesResponseDto;
import racingcar.dto.GameResultResponseDto;
import racingcar.dto.StartGameRequestDto;
import racingcar.dto.car.GameHistoriesResponseDto;
import racingcar.dto.car.GameResultResponseDto;
import racingcar.dto.car.StartGameRequestDto;
import racingcar.service.RacingCarService;

import javax.validation.Valid;
import java.util.List;

@RequestMapping("/plays")
@RestController
public class RacingGameWebController {
begaonnuri marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -29,23 +27,25 @@ public RacingGameWebController(final RacingCarService racingCarService) {

@GetMapping
public ResponseEntity<List<GameHistoriesResponseDto>> findGameHistories() {
return new ResponseEntity<>(racingCarService.findAllGameHistories(), HttpStatus.OK);
return ResponseEntity.ok()
.body(racingCarService.findAllGameHistories());
}

@PostMapping
public ResponseEntity<GameResultResponseDto> startGame(@RequestBody final StartGameRequestDto request) {
public ResponseEntity<GameResultResponseDto> startGame(@Valid @RequestBody final StartGameRequestDto request) {
Cars cars = makeCars(request.getNames());
TryCount tryCount = makeTryCount(request.getCount());

return new ResponseEntity<>(racingCarService.startRace(cars, tryCount), HttpStatus.OK);
return ResponseEntity.status(HttpStatus.CREATED)
.body(racingCarService.startRace(cars, tryCount));
}

public Cars makeCars(final String input) {
private Cars makeCars(final String input) {
List<String> carNames = List.of(input.split(CAR_NAME_DELIMITER));
return Cars.from(carNames);
}

public TryCount makeTryCount(final int input) {
private TryCount makeTryCount(final int input) {
return new TryCount(input);
}
}
21 changes: 11 additions & 10 deletions src/main/java/racingcar/dao/GameResultDao.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package racingcar.dao;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Component;
import racingcar.domain.Cars;
import racingcar.domain.TryCount;
import racingcar.dto.car.GameResultResponseDto;
import racingcar.dto.car.PlayerHistoryDto;

import java.sql.PreparedStatement;
import java.sql.Statement;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Component;
import racingcar.domain.Cars;
import racingcar.domain.TryCount;
import racingcar.dto.GameResultResponseDto;
import racingcar.dto.PlayerHistoryDto;

@Component
public class GameResultDao {
Expand Down Expand Up @@ -50,7 +51,7 @@ public GameResultResponseDto saveGame(final Cars cars, final TryCount tryCount)
return savePlayersStatus(cars, gameId);
}

public Long saveGameHistory(final TryCount tryCount) {
private Long saveGameHistory(final TryCount tryCount) {
String sql = "INSERT INTO game (trialCount, dateTime) VALUES (?, ?)";
KeyHolder keyHolder = new GeneratedKeyHolder();

Expand All @@ -64,7 +65,7 @@ public Long saveGameHistory(final TryCount tryCount) {
return Objects.requireNonNull(keyHolder.getKey()).longValue();
}

private GameResultResponseDto savePlayersStatus(final Cars cars, final long gameId) {
private GameResultResponseDto savePlayersStatus(final Cars cars, final Long gameId) {
String sql = "INSERT INTO player (game_id, name, position, isWinner) VALUES (?, ?, ?, ?)";

cars.getCars()
Expand Down
23 changes: 0 additions & 23 deletions src/main/java/racingcar/dto/StartGameRequestDto.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package racingcar.dto;
package racingcar.dto.car;

import racingcar.domain.Car;

Expand All @@ -16,10 +16,6 @@ public static CarStatusResponseDto toDto(final Car car) {
return new CarStatusResponseDto(car.getCarName(), car.getDistance());
}

public static CarStatusResponseDto toDto(final String name, final int position) {
return new CarStatusResponseDto(name, position);
}

public static CarStatusResponseDto toDto(final PlayerHistoryDto playerHistoryDto) {
return new CarStatusResponseDto(playerHistoryDto.getName(), playerHistoryDto.getPosition());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package racingcar.dto;
package racingcar.dto.car;

import java.util.List;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package racingcar.dto;
package racingcar.dto.car;

import racingcar.domain.Cars;

import java.util.List;
import java.util.stream.Collectors;
import racingcar.domain.Cars;

public class GameResultResponseDto {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package racingcar.dto;
package racingcar.dto.car;

public class PlayerHistoryDto {

Expand Down
33 changes: 33 additions & 0 deletions src/main/java/racingcar/dto/car/StartGameRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package racingcar.dto.car;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

public class StartGameRequestDto {

@NotBlank(message = "이름을 입력해주세요.")
private String names;

@NotNull(message = "시도 회수를 입력해주세요.")
@Min(value = 2, message = "최소 횟수는 2입니다.")
@Max(value = 10, message = "최대 횟수는 10입니다.")
Comment on lines +10 to +15
Copy link
Member

Choose a reason for hiding this comment

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

@Valid를 사용해주셨네요! 어떤 장단점이 있었나요?

Copy link
Author

Choose a reason for hiding this comment

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

  1. 제가 생각한 @Valid의 장점은 다음과 같습니다.
  • 사용자의 입력에 대한 데이터 유효성을 검사할 수 있습니다. (DB에 잘못된 데이터가 들어갈 확률이 낮아집니다!)
  • 사용하다보니 규모가 커질 경우 예외를 검증하는 코드를 따로 작성하지 않아도 돼서 생산성에 있어서 좋다고 생각합니다.

  1. 제가 생각한 단점은 다음과 같습니다.
  • 어노테이션 사용법을 알아야하기 때문에, 모르는 팀원이 있으면 오히려 시간이 더 걸릴 수 있습니다.
  • 또한 직접 사용하다 보니 어떤 예외를 던지는지에 대해 공부를 따로 했습니다. 제가 아직은 자세히 모르는 것일 수도 있겠지만, 커스텀 예외를 따로 만들어서 발생시키기 힘든 것 같습니다.

Copy link
Member

Choose a reason for hiding this comment

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

좋네요 👍🏻 여기서 조금 더 보완해드리자면,

사용하다보니 규모가 커질 경우 예외를 검증하는 코드를 따로 작성하지 않아도 돼서 생산성에 있어서 좋다고 생각합니다.

편리한 점이 있어도 웹 요청에 한정해서 비교적 간단한 검증만 이루어져야하고, 핵심 도메인에 대한 검증까지 이를 대체하려고 하면 안됩니다. 복잡한 도메인 로직을 타기 전에 간단한 검증을 도와주는 장치이지 비즈니스 로직을 대체하는 장치가 아니라는 점이 중요합니다 🙂

커스텀 예외를 따로 만들어서 발생시키기 힘든 것 같습니다.

이는 좋게 작용할 여지도 있다고 생각해요. 잘못된 요청에 대해선 일괄로 가벼운 처리와 4xx 응답 코드를 던져주는처리를 할 수 있고, 또 커스텀 예외가 필요하다면 MethodArgumentNotValidException을 상속받는 커스텀 예외를 만들어서 처리할 수도 있을 것 같네요!

private Integer count;

public StartGameRequestDto() {
}

public StartGameRequestDto(final String names, final int count) {
this.names = names;
this.count = count;
}

public String getNames() {
return names;
}

public int getCount() {
return count;
}
}
24 changes: 24 additions & 0 deletions src/main/java/racingcar/dto/response/ErrorResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package racingcar.dto.response;

public class ErrorResponse {

private final int code;
private final String message;

public ErrorResponse(int code, String message) {
this.code = code;
this.message = message;
}

public static ErrorResponse send(final int code, final String message) {
return new ErrorResponse(code, message);
}

public int getCode() {
return code;
}

public String getMessage() {
return message;
}
}
15 changes: 8 additions & 7 deletions src/main/java/racingcar/service/RacingCarService.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package racingcar.service;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import racingcar.dao.GameResultDao;
import racingcar.domain.Cars;
import racingcar.domain.TryCount;
import racingcar.dto.CarStatusResponseDto;
import racingcar.dto.GameHistoriesResponseDto;
import racingcar.dto.GameResultResponseDto;
import racingcar.dto.PlayerHistoryDto;
import racingcar.dto.car.CarStatusResponseDto;
import racingcar.dto.car.GameHistoriesResponseDto;
import racingcar.dto.car.GameResultResponseDto;
import racingcar.dto.car.PlayerHistoryDto;
import racingcar.utils.RandomPowerGenerator;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class RacingCarService {

Expand Down
5 changes: 3 additions & 2 deletions src/main/java/racingcar/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package racingcar.view;

import racingcar.dto.car.CarStatusResponseDto;
import racingcar.dto.car.GameHistoriesResponseDto;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import racingcar.dto.CarStatusResponseDto;
import racingcar.dto.GameHistoriesResponseDto;

public class OutputView {

Expand Down
1 change: 1 addition & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL
spring.datasource.driver-class-name=org.h2.Driver

spring.config.activate.on-profile=test