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단계 - 웹 자동차 경주] 제이미(임정수) 미션 제출합니다. #183

Merged
merged 24 commits into from
Apr 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5d4c6c0
refactor: 반환 타입 수정
JJ503 Apr 18, 2023
dc95ea0
fix: 테이블 생성 충돌 문제 해결
JJ503 Apr 19, 2023
eec0887
feat: console 게임 코드 추가
JJ503 Apr 19, 2023
a8e5efe
refactor: 비즈니스 로직을 service로 이동
JJ503 Apr 19, 2023
ea1c77e
refactor: 테스트 필요한 bean 범위에 따른 annotation 수정
JJ503 Apr 19, 2023
05e9802
docs: 기능 목록 추가
JJ503 Apr 19, 2023
b457b72
feat: 전체 아이디 불러오기 기능 추가
JJ503 Apr 19, 2023
372aa11
feat: 아이디에 따른 우승자 가져오기 기능 추가
JJ503 Apr 19, 2023
ff4645b
feat: 아이디에 따른 레이싱카 정보 가져오 기능 추가
JJ503 Apr 19, 2023
4e460d0
feat: 전체 게임 결과 목록 가져오기 기능 추가
JJ503 Apr 20, 2023
ddd3a5a
feat: 웹 컨트롤러 핸들러 메서드 매핑
JJ503 Apr 20, 2023
3fbcf65
refactor: 중복 코드 제거
JJ503 Apr 20, 2023
4994fdc
rename: 패키지 명 수정 및 분리
JJ503 Apr 20, 2023
1475dcc
fix: 서비스 생성자 자동주입 오류 문제 해결
JJ503 Apr 20, 2023
f998022
fix: 랜덤 숫자 생성기 주입 위치 수정
JJ503 Apr 20, 2023
604966f
docs: 기능 목록 완료 체크
JJ503 Apr 20, 2023
ab3dd50
feat: exceptionHandler 추가
JJ503 Apr 21, 2023
ce5e165
refactor: Mapping 경로 중복 제거
JJ503 Apr 21, 2023
00f45a5
fix: 잘못된 테스트 빈 생성 문제 해결
JJ503 Apr 24, 2023
72a9705
fix: id 초기화가 되지 않는 문제 해결
JJ503 Apr 24, 2023
82f8a15
feat: Entity 객체 추가
JJ503 Apr 24, 2023
a170c7b
refactor: dto 클래스명 수정
JJ503 Apr 24, 2023
be898cb
refactor: assertion들을 asserTaht으로 묶음
JJ503 Apr 24, 2023
58dfcf1
refactor: 변수 네이밍 수정
JJ503 Apr 24, 2023
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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
- [x] 코드 가져오기

2. 웹 요청/응답 구현하기
- [x] /plays에 대한 컨트롤러 생성
- [x] /plays(post)에 대한 기능 생성
- [x] json request 받아 객체 생성
- [x] 결과 json 형태로 response
- [x] /plays(get)에 대한 기능 생성
- [x] 호출 시 json 형태로 결과 반환

3. DB 연동하기
- [x] H2 DB를 연동
Expand All @@ -16,3 +18,6 @@
- 플레이어 별 최종 이동 거리 (이름(name), 최종 위치(position))
- 우승자(winners)
- 플레이한 날짜/시간
- [x] 전체 결과 정보 가져오기
- 우승자
- 플레이어 별 최종 이동 거리
10 changes: 10 additions & 0 deletions src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package racingcar;

import racingcar.controller.RacingCarConsoleController;

public class Application {
public static void main(String[] args) {
RacingCarConsoleController racingCarConsoleController = new RacingCarConsoleController();
racingCarConsoleController.runGame();
}
}
49 changes: 49 additions & 0 deletions src/main/java/racingcar/controller/RacingCarConsoleController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package racingcar.controller;

import racingcar.domain.Cars;
import racingcar.service.RacingCarService;
import racingcar.util.RandomNumberGenerator;
import racingcar.view.InputView;
import racingcar.view.OutputView;

public class RacingCarConsoleController {
private final InputView inputView = new InputView();
private final OutputView outputView = new OutputView();
private final RacingCarService racingCarService = new RacingCarService();
private final RandomNumberGenerator randomNumberGenerator = new RandomNumberGenerator();

public void runGame() {
Cars cars = initCarData();
movePerRounds(cars, setTryCount());
outputView.printWinner(cars);
}

private Cars initCarData() {
outputView.printCarNameMessage();

try {
String carNames = inputView.inputCarNames();
return racingCarService.getCars(carNames);
Comment on lines +25 to +26
Copy link
Member Author

Choose a reason for hiding this comment

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

콘솔과 웹 게임의 중복 코드를 제거해 주기 위해 ConsoleController에서 사용하는 메서드들을 public으로 바꿔주었습니다.
이와 같은 방법이 맞는 것인지 아직 감이 오지 않는 상태입니다.
카프카의 의견이 궁금합니다...!

Choose a reason for hiding this comment

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

사실 보통의 상황이라면 이렇게 콘솔과 웹을 동시에 구현하지는 않을듯해요. 그러나 미션 요구사항이므로...특수한 상황임을 감안하면 이해가 되는 부분입니다.
일반적인 구조라면, Controller는 요청에 대한 라우팅만 수행하는 것이 맞기는 합니다. 위의 runGame 메소드같은 경우도, 요청이 들어올때 Service에서 수행해줘야 하지요. 일단 RacingCarWebController에서 사양에 맞게 잘 구현되어 있으므로, 이 부분에 대해서는 감안하겠습니다.

Copy link
Member Author

Choose a reason for hiding this comment

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

RacingCarConsoleController 클래스의 runGame()에 존재하는 로직은 모두 View를 위한 로직인데 그래도 Service에 있어야 하는 것일까요?

Choose a reason for hiding this comment

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

View를 위한 로직이라는 말이 참 애매한데... 현재 보면 컨트롤러가 View 레이어와 강한 의존을 가지고 있다고 느껴져요.
만약 완벽하게 리팩토링을 하고 싶다면, outputview와 inputview를 json DTO를 통해 통신하도록 완벽히 구현해서 WebController와 동일한 서비스를 사용하도록 해볼 수 있어 보이긴 합니다. 다만 이미 WebController 부분의 구현에서 해당 스펙을 구현했으니, 이번 미션에서 그 정도까지의 작업을 할 필요는 없다고 판단했습니다.

MVC 구조에서 서비스가 하는 역할에 대해 잘 이해하고 있다면, 크게 문제가 되어 보이지는 않습니다.

} catch (Exception e) {
System.out.println(e.getMessage());
return initCarData();
}
}

private int setTryCount() {
outputView.printTryCountMessage();

try {
int tryCount = inputView.inputTryCount();
return racingCarService.getTrialCount(tryCount);
} catch (Exception e) {
System.out.println(e.getMessage());
return setTryCount();
}
}

private void movePerRounds(Cars cars, int tryCount) {
outputView.printResultMessage();
racingCarService.playGame(cars, tryCount, randomNumberGenerator);
}
}
64 changes: 0 additions & 64 deletions src/main/java/racingcar/controller/RacingCarController.java

This file was deleted.

37 changes: 37 additions & 0 deletions src/main/java/racingcar/controller/RacingCarWebController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package racingcar.controller;

import org.springframework.web.bind.annotation.*;
import racingcar.dto.RacingCarResponse;
import racingcar.dto.RacingGameRequest;
import racingcar.service.RacingCarService;
import racingcar.util.NumberGenerator;

import java.util.List;

@RestController
@RequestMapping("plays")
public class RacingCarWebController {

private final RacingCarService racingCarService;
private final NumberGenerator numberGenerator;

public RacingCarWebController(RacingCarService racingCarService, NumberGenerator numberGenerator) {
this.racingCarService = racingCarService;
this.numberGenerator = numberGenerator;
}

@PostMapping
public RacingCarResponse createGame(@RequestBody RacingGameRequest racingGameRequest) {
return racingCarService.play(racingGameRequest, numberGenerator);
}

@GetMapping
public List<RacingCarResponse> loadAllGame() {
return racingCarService.findAllGame();
}

@ExceptionHandler
public void handle(Exception exception) {
System.out.println(exception.getMessage());
}
Comment on lines +33 to +36
Copy link
Member Author

@JJ503 JJ503 Apr 24, 2023

Choose a reason for hiding this comment

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

1단계 피드백주신 내용에 따라 ExceptionHandler를 추가해보았습니다.

그런데 html 코드를 살펴보니 다음과 같이 예외가 발생하면 무조건 Error()를 던지게 되어 있습니다.

.then(response => {
  if (response.ok) {
    return response.json();
  } else {
    throw new Error("Network response was not ok.");
  }
})

이 경우 Controller에서 예외를 반환해 주어야 하는 걸까요?
혹은 현재처럼 예외를 던지지 않고 콘솔에 예외 메시지만 출력해 주는 것이 좋을까요?

Choose a reason for hiding this comment

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

제 생각을 정리해 보겠습니다.

  • 현재 html 내의 js 코드를 보면, response의 상태값이 ok(200)가 아니면 에러를 발생시키고 있습니다.
  • 즉 예기치 못한 상황에서 생긴 4xx 에러(예: 컨트롤러의 path를 잘못 수정해 404 에러 발생)나 5xx 에러 (핸들링되지 못한 예외 발생 등) 에 대해 에러를 발생시킨다는 의미입니다.
  • 그러나 ExceptionHandler로 예외를 캐치했을 경우, 우리가 원하는 것은 그 예외를 다시 발생시키는 것이 아니라, 예외의 내용을 전달하는 것이겠지요? 현재는 해당 내용을 콘솔 로그로 찍어주고 있고요. (처음 해보는 것인데, 이 정도의 성취도 충분히 잘한 것이라고 생각합니다 💯 )
  • 그렇다면 이제 우리가 해야 할 것은, "예외가 발생했고 handler가 예외를 핸들링했다면, 적절한 상태로 적절한 DTO에 에러 메시지를 보내주면 되지 않을까?" 라고 생각합니다.

Choose a reason for hiding this comment

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

저라면, 아래처럼 구현할 것으로 생각됩니다. 한번 생각한 대로 정리해 볼게요.

  • field로 errorMessage를 포함하는 ErrorDTO 클래스를 만들어 준다.
  • 예외를 handling할 경우, ResponseDTO를 반환해 준다.
  • 예외가 발생했고 handling이 되면, 400 bad request 상태를 ResponseEntity에 설정해서 반환한다.
  • 현재 프론트엔드 코드를 일부 수정하여, Error 발생시 response.errorMessage를 인자로 삼도록 한다.

혹시 이해가 가지 않는 부분이 있다면 코멘트 부탁드립니다. 👍

}
20 changes: 0 additions & 20 deletions src/main/java/racingcar/dao/RacingCarDao.java

This file was deleted.

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

public class RacingCarDto {
public class RacingCarData {

private final String name;
private final int position;


public RacingCarDto(String name, int position) {
public RacingCarData(String name, int position) {
this.name = name;
this.position = position;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package racingcar.domain;
package racingcar.dto;

import java.util.List;

public class GameResultDto {
public class RacingCarResponse {

private final String winners;
private final List<RacingCarDto> racingCars;
private final List<RacingCarData> racingCars;

public GameResultDto(String winners, List<RacingCarDto> racingCars) {
public RacingCarResponse(String winners, List<RacingCarData> racingCars) {
this.winners = winners;
this.racingCars = racingCars;
}
Expand All @@ -16,7 +16,7 @@ public String getWinners() {
return winners;
}

public List<RacingCarDto> getRacingCars() {
public List<RacingCarData> getRacingCars() {
return racingCars;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package racingcar.domain;
package racingcar.dto;

public class GameInforamtionDto {
public class RacingGameRequest {

private final String names;
private final int count;

public GameInforamtionDto(String names, int count) {
public RacingGameRequest(String names, int count) {
this.names = names;
this.count = count;
}
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/racingcar/entity/RacingCar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package racingcar.entity;

public class RacingCar {

private final long id;
private final String name;
private final int position;
private final long resultId;

public RacingCar(long id, String name, int position, long resultId) {
this.id = id;
this.name = name;
this.position = position;
this.resultId = resultId;
}

public long getId() {
return id;
}

public String getName() {
return name;
}

public int getPosition() {
return position;
}

public long getResultId() {
return resultId;
}
}
34 changes: 34 additions & 0 deletions src/main/java/racingcar/entity/Result.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package racingcar.entity;

import java.time.LocalDateTime;

public class Result {

private final long id;
private final int trialCount;
private final String winners;
private final LocalDateTime createdAt;

public Result(long id, int trialCount, String winners, LocalDateTime createdAt) {
this.id = id;
this.trialCount = trialCount;
this.winners = winners;
this.createdAt = createdAt;
}

public long getId() {
return id;
}

public int getTrialCount() {
return trialCount;
}

public String getWinners() {
return winners;
}

public LocalDateTime getCreatedAt() {
return createdAt;
}
}
39 changes: 39 additions & 0 deletions src/main/java/racingcar/repository/RacingCarDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package racingcar.repository;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import racingcar.domain.Car;
import racingcar.entity.RacingCar;

import java.util.List;

@Repository
public class RacingCarDao {

private JdbcTemplate jdbcTemplate;

public RacingCarDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public void insert(Car car, long resultId) {
String sql = "insert into racing_cars (name, position, result_id) values (?, ?, ?)";
jdbcTemplate.update(sql, car.getName(), car.getLocation(), resultId);
}

private final RowMapper<RacingCar> actorRowMapper = (resultSet, rowNum) -> {
RacingCar racingCar = new RacingCar(
resultSet.getLong("id"),
resultSet.getString("name"),
resultSet.getInt("position"),
resultSet.getLong("result_id")
);
return racingCar;
};

public List<RacingCar> findBy(long resultId) {
String sql = "select * from racing_cars where result_id = ?";
return jdbcTemplate.query(sql, actorRowMapper, resultId);
}
}
Loading