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 16 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.

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

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.RestController;
import racingcar.dto.GameInforamtionDto;
import racingcar.dto.GameResultDto;
import racingcar.service.RacingCarService;
import racingcar.util.NumberGenerator;

import java.util.List;

@RestController
public class RacingCarWebController {

private final RacingCarService racingCarService;
private final NumberGenerator numberGenerator;

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

@PostMapping("plays")
public GameResultDto createGame(@RequestBody GameInforamtionDto gameInforamtionDto) {
return racingCarService.play(gameInforamtionDto, numberGenerator);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

ResponseEntity가 아닌 객체를 바로 반환하도록 함
리뷰 답변 참고

Copy link
Member Author

Choose a reason for hiding this comment

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

Controller와 Service의 책임을 어떻게 부여해야 할지 감이 오지 않습니다.
현재는 중복코드를 제거해 주기 위해 모든 비즈니스 로직을 서비스로 넘긴 상태인데 괜찮은 구조일까요?

레벨1에서는 콘솔을 view로 사용하였고 Service 레이어의 필요성을 느끼지 못했기에 현재 Service에 있는 로직은 Controller에 있었습니다.
이후 체스 미션에서 DB를 사용하게 되면서 Service는 Controller와 dao를 연결해 주는 중간 다리 역할로 사용하게 되었었습니다.
그런데 이를 모두 Service에 옮기게 되니 와닿지 않는 상태입니다...!

Choose a reason for hiding this comment

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

현재 구조를 잘 짜주었다고 생각됩니다.
제가 생각하는 Controller의 역할은, 요청을 받아 적절한 서비스에 전달 후 서비스에서 결과값을 전달받아 반환하는 라우팅의 역할입니다. 그에 비해 Service의 역할은, Dao/repository의 연산 순서와 트랜잭션(이 부분은 이후 미션에서 학습할 듯 합니다) 을 지켜주면서 DB와의 통신을 수행해 주고, 추가로 필요한 연산(엔티티/도메인 클래스에 대한 조작)을 수행해 주는 것이지요.

이러한 시각에서 보았을 때 저는 현재 구조에 큰 문제는 없다고 생각합니다. 😄

Copy link
Member Author

Choose a reason for hiding this comment

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

자세한 답변 감사합니다!
서비스에서 도메인에 접근해도 괜찮은 것인가에 대해 고민이었는데 덕분에 구조에 대해 조금은 이해가 되었습니다!


@GetMapping("plays")
public List<GameResultDto> loadAllGame() {
return racingCarService.findAllGame();
}
}
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,4 +1,4 @@
package racingcar.domain;
package racingcar.dto;

public class GameInforamtionDto {

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

import java.util.List;

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

public class RacingCarDto {

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

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import racingcar.domain.Car;
import racingcar.dto.RacingCarDto;

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);
}

public List<RacingCarDto> findBy(long resultId) {
String sql = "select name, position from racing_cars where result_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.

현재 제 쿼리문과 Dto를 살펴보면 table 데이터의 한 행을 가져오는 것이 아니라 행에서 필요한 정보만 가져오고 있습니다.
해당 방법과 한 행을 모두 가져오는 것 중 어떤 것이 더 적절하다고 생각하시나요?

이를 고민하며 추가적으로 든 의문은 DTO와 Entity의 차이입니다.
Entity는 Table과 1:1 매핑이 되는 Dto와 같은 객체로 알고 있는데, Dto와의 역할은 아예 다른 것일까요?
만약 원하는 정보만이 아니라 한 행을 가져오게 된다면 Dto보단 Entity가 더 적절할까요?

Choose a reason for hiding this comment

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

제가 생각하는 바를 정리해보면 아래와 같습니다.

  • Dao는 입력(삽입) 및 출력(조회) 시 Entity 값을 주는 것이 좋습니다.
    • dao 혹은 repository가 하나의 entity에 대한 CRUD 연산을 수행한다고 개발자들이 인식하기 때문입니다. 예를 들어 하나의 dao에서 두세개의 엔티티에 대한 연산을 수행하거나, RacingCarDao가 Player entity를 인자로 받는다고 생각하면 이상하지 않나요?
    • 만약 여러 엔티티를 조회해와서 연산해야 한다면, 서비스에서 적절히 dao를 사용해 조회한 값들을 바탕으로 연산하면 됩니다.
    • 특정한 값만 필요하다면, 상황에 따라서는 그에 맞는 메소드를 만들 수 있습니다. 예를 들어 jpa 기반의 repository들은, String findNameById(Long id) 처럼, 특정 필드만 조회하는 메소드를 지원하고 있습니다. 다만 여러 필드를 가져온다면, 저는 차라리 엔티티째로 받아 적절히 연산하는 것이 가독성이나 확장성 면에서 더 낫다고 생각합니다. 쿼리의 효율도 큰 차이가 없을듯하고요.
  • 그리고 저는 DTO가 dao 연산에 들어가는 것에는 반대하고 있습니다.
    • DTO는 컨트롤러 및 서비스가 다른 레이어와 통신 시 사용하는 클래스입니다. 그런데 이것이 dao와의 연산에서 사용되면, dao가 컨트롤러 등에 종속될 수 있습니다.
    • 서비스 레이어는 엔티티 클래스를 직접 다루는 레이어이기 때문에, 엔티티로 dao와 통신하는 것이 책임상의 문제가 되지는 않습니다.
    • 물론 이런 경우가 있을 수 있지요. id가 auto_increment여서 해당 필드를 비워서 보내야 하는 경우엔 어떻게 하면 좋을까요? 저는 그러한 경우에는, 엔티티에 빌더 패턴을 적용하고 id는 null로 설정해서 보내줍니다. 이것이 좋은 방법이 아닌 것처럼 보일수도 있으나, 해당 연산을 수행하기 위해 별도의 클래스를 도입하거나 Map으로 인자를 전달하는 것보다는 가독성 면에서 낫다고 생각하기 때문입니다.

제 생각을 정리하다 보니 다소 두서없게 글이 나왔네요.
혹시 이해가 잘 되지 않는 부분이 있거나, 납득이 되지 않는 부분이 있다면 편하게 코멘트 달아주세요.
제이미가 어떻게 생각하는지 의견을 들어보고, 함께 고민하며 더 좋은 방향을 결정해보고 싶습니다. 👍

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.

자세한 답변 감사합니다!
일단 DTO를 사용하면 안 되는 이유에 대해서 카프카의 설명 덕분에 이해하게 되었습니다!
또한, 저 역시 확장성을 생각했을 때는 Entity로 가져오는 것이 더 낫다는 이야기에 공감합니다.
그래서 우선 Entity를 반환하는 방법으로 수정해 보았습니다. (로직 바로가기)

처음에는 사용하지 않는 데이터도 있으니, dto를 사용해야 하지 않을까? 라고, 생각했습니다.
그런데 확장성을 생각하다 보니 Entity가 더 적절한지 고민하게 되며 질문하게 되었습니다.
그런데 select를 통해 *을 사용해야 하는 것을 지양해야 한다는 글을 보았습니다. (select 시 * 사용 지양 블로그 글)
저 역시 처음에 부분 값만 가져오려고 한 이유는 불필요한 데이터를 저장할 필요까지 있는가에서 시작되었기 때문에 이에 대한 카프카의 의견이 궁금합니다..
혹은 제가 카프카의 의견을 잘못 이해한 것인지도 궁금합니다.

추가적으로 만약 전체가 아니라 특정 데이터들만 가져오게 된다면 해당 객체는 무슨 객체에 속하는 것일까요?
DTO도 Entity도 아닌 것 같아 여쭤봅니다…!

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를 null로 설정하고 보내라는 이야기가 있는 이 부분에 대해 제대로 이해하지 못했습니다.
Select를 통해 값을 가져오는 경우는 null로 설정할 필요가 없는데, 카프카가 말씀하신 부분은 dao에 값을 넘길 때를 이야기인 걸까요?
만약 그렇다면 현재처럼 객체, 특정 값을 넘기는 것 보다 db에 insert 시에도 Entity를 사용해 Service에서 dao로 넘겨야 한다는 것을 말씀해 주신 걸까요?

Choose a reason for hiding this comment

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

피드백에 대해 학습해서 빠르게 수정해주신 부분 좋습니다 💯
repository(dao)를 어떻게 구성하면 좋을지에 대해 고민을 해볼 수 있었네요.

그리고 이제 코멘트 남겨주신 부분에 대해서도 제 생각을 정리해 보겠습니다.
(다만 저는 주로 jpa+querydsl 스펙을 사용하므로, jdbc 구현에 대해 좋은 코멘트를 드리지 못할 수도 있습니다.)

Choose a reason for hiding this comment

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

적어주신대로 select * 를 지양하는 것은 좋은 습관이라고 생각합니다.
실제로 jpa 등에서 자동으로 생성되는 쿼리를 보면, *을 쓰지 않고 각각의 필드를 매핑해 주는 방식이거든요.
다만 저는 그렇다고 해도, 엔티티에 필요한 필드 전체를 쿼리에 명시해주면 되지 않나, 라고 생각하고 있습니다. �

  • 만약 테이블에 5개의 컬럼이 있는데, 엔티티에는 3개 필드만이 존재하고, 쿼리는 그 3개 컬럼을 조회한다 -> 이건 이상하다고 보는 거고요
  • 테이블에 5개 컬럼이 있고, 엔티티에는 5개 필드가 존재한다. 쿼리는 5개 컬럼을 조회한다 -> 이건 자연스럽다고 생각하고 있어요.
  • 테이블에 5개 컬럼이 있고, 쿼리를 통해 그 중 2개 컬럼을 Map에 담아 반환하려고 한다 -> 요거까지도 자연스럽지 않나 라는 생각이 듭니다.

다만 db의 값 중 필요한 값 일부만을 가져오는 것이 현재 미션에서 필요할지에 대해서도 의문이 들기는 하지만요.

그리고 특정 데이터들만 가져오게 된다면, 아무래도 entity보다는 ResultSet을 VO 클래스에 매핑하게 되지 않을까 생각이 됩니다. 저번 리뷰에서 제가 그러한 경우에 사용하는 클래스 역시 엔티티라고 지칭했는데,그보다는 VO라는 표현이 더 맞지 않을까 싶었어요.

return jdbcTemplate.query(sql,
(rs, rowNum) -> {
String name = rs.getString("name");
int position = rs.getInt("position");
RacingCarDto racingCarDto = new RacingCarDto(name, position);
return racingCarDto;
}, resultId);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package racingcar.dao;
package racingcar.repository;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;

import java.sql.PreparedStatement;
import java.util.List;

@Repository
public class ResultDao {
Expand All @@ -28,4 +29,14 @@ public long insert(int trialCount, String winners) {

return keyHolder.getKey().longValue();
}

public List<Long> findAllId() {
String sql = "select id from results";
return jdbcTemplate.queryForList(sql, Long.class);
}

public String findWinnerBy(long id) {
String sql = "select winners from results where id = ?";
return jdbcTemplate.queryForObject(sql, String.class, 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를 모두 가져와 results 테이블에서는 각 아이디의 우승자를 가져오고 racing_cars 테이블에서는 각 아이디를 result_id로 갖고 있는 행들에 대한 List를 가져오게 되어 있습니다.
그렇게 가져온 정보들은 Service에서 List<>로 다시 만들어 Controller에 넘겨주게 됩니다.

그런데 쿼리문 중 join이 있는 것을 알고 있는데 이를 사용하는 것이 더 효율적인지 궁금합니다.
또한, join을 사용해 우승자, 참여 레이싱카 리스트를 한 번에 가져오게 된다면 어떤 RacingCarDao와 ResultsDao 중 어떤 클래스에 있는 것이 적절한지 궁금합니다.

Choose a reason for hiding this comment

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

만약 dao에 join을 꼭 적용해야 한다면, 저라면 join으로 얻게 되는 값과 매핑되는 entity 클래스를 만들어주고 그 엔티티에 대한 연산만 수행하도록 dao의 역할을 제한할 듯 하네요.
다만 저는 현재 미션 레벨에서 join을 직접 사용하는 것을 권하지는 않습니다.
이후 jpa, querydsl 등의 기술스택을 익힐 경우 더 편리하게 구현할 수 있기도 하고, 현재 레벨에서 join을 포함한 쿼리를 작성하면서 생기는 문제점들도 있다고 생각하기 때문입니다. (최적화가 어렵고, 쿼리의 지속적인 관리가 잘 될지도 조심스러우며, 이후의 변화에 동적으로 대응하기 어려운 점 등...)

Choose a reason for hiding this comment

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

다만 이렇게 더 좋은 방법에 대해 고민해본 부분은 매우 좋습니다 💯
질문해준 내용에 대해 해답을 드리기보다 권하지 않는다는 말로 끝내게 되어서 아쉽지만요,
그래도 이 부분은 좀 더 학습을 진행해보면서 차근차근 접근하는 쪽이 더 효율적일 것이라고 생각이 되었습니다.

Copy link
Member Author

Choose a reason for hiding this comment

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

join 사용에 대한 의견 감사합니다!
join으로 얻게 되는 값은 존재하는 테이블이 아닌데 해당 데이터도 Entity라고 호칭하나요?
join을 통해 둘을 합친 테이블이기 때문에 Entity라고 보는 것일까요?
그렇다면 select 등을 통해 만들어진 정보도 결국 테이블이라고 책에서 봤던 것으로 기억하는데 이도 Entity라고 볼 수 있을까요?

join과 관련된 추가적인 질문으로 현재 foreign key를 사용하고 있습니다.
이에 대해 join을 사용하지 않기에 foreign key로 만들지 않았다는 이야기를 몇 번 들었는데 정말 의미가 없는 것일까요?
일단 제가 foreign key를 사용한 이유는 racing_cars에 있는 result_id 데이터는 꼭 results 테이블에 있는 아이디어야 하기 때문입니다.
이를 보장해 주기 위해 사용했다고 생각했는데 이에 대한 카프카의 의견이 궁금합니다!

Choose a reason for hiding this comment

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

join으로 얻게 되는 값도 위의 리뷰에 기재한 것처럼, Entity라기보다는 ResultSet을 VO 클래스에 매핑하는 식이 되지 않을까 싶네요.
제가 이 부분에 대해서는 용어를 부적절하게 사용했다는 생각이 듭니다. 환기해주셔서 감사해요 👍

그리고 FK를 사용하는 것은 join과는 별개로 좋다고 저는 생각해요!
FK에 대한 제약조건이 많기 때문에, 부적절한 데이터가 발생하는 것(예: 잘못된 매핑, 실제로 없는 데이터와의 연관관계 생성 등)을 막을 수 있기 때문입니다.
그래서 저도 제이미의 의견에 적극 공감합니다. 이번 미션을 통해 외래키 관련하여 생각을 잘 정리해서 적어주셨는데, 정말 잘해주셨어요 💯 💯

}
68 changes: 63 additions & 5 deletions src/main/java/racingcar/service/RacingCarService.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,85 @@
package racingcar.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import racingcar.dao.RacingCarDao;
import racingcar.dao.ResultDao;
import racingcar.domain.Car;
import racingcar.domain.Cars;
import racingcar.dto.GameInforamtionDto;
import racingcar.dto.GameResultDto;
import racingcar.dto.RacingCarDto;
import racingcar.repository.RacingCarDao;
import racingcar.repository.ResultDao;
import racingcar.util.NumberGenerator;
import racingcar.validation.Validation;

import java.util.ArrayList;
import java.util.List;

@Service
public class RacingCarService {

private final ResultDao resultDao;
private final RacingCarDao racingCarDao;
private ResultDao resultDao;
private RacingCarDao racingCarDao;

@Autowired
public RacingCarService(ResultDao resultDao, RacingCarDao racingCarDao) {
this.resultDao = resultDao;
this.racingCarDao = racingCarDao;
}

public void insertGame(int trialCount, Cars cars) {
public RacingCarService(){}

public GameResultDto play(GameInforamtionDto gameInforamtionDto, NumberGenerator numberGenerator) {
Cars cars = getCars(gameInforamtionDto.getNames());
int trialCount = getTrialCount(gameInforamtionDto.getCount());

List<RacingCarDto> racingCars = playGame(cars, trialCount, numberGenerator);
insertGame(trialCount, cars);

return new GameResultDto(cars.getWinnerCars(), racingCars);
}

public Cars getCars(String names) {
Validation.validateCarNames(names);
return new Cars(names);
}

public int getTrialCount(int trialCount) {
Validation.validateTryCount(trialCount);
return trialCount;
}

public List<RacingCarDto> playGame(Cars cars, int trialCount, NumberGenerator numberGenerator) {
for (int count = 0; count < trialCount; count++) {
cars.moveForRound(numberGenerator);
}

List<RacingCarDto> racingCars = new ArrayList<>();
for (Car car : cars.getCars()) {
RacingCarDto racingCarDto = new RacingCarDto(car.getName(), car.getLocation());
racingCars.add(racingCarDto);
}
return racingCars;
}

private void insertGame(int trialCount, Cars cars) {
long resultId = resultDao.insert(trialCount, cars.getWinnerCars());

for (Car car : cars.getCars()) {
racingCarDao.insert(car, resultId);
}
}

public List<GameResultDto> findAllGame() {
List<Long> gameIds = resultDao.findAllId();
List<GameResultDto> gameResults = new ArrayList<>();

for (Long gameId : gameIds) {
String winners = resultDao.findWinnerBy(gameId);
List<RacingCarDto> racingCars = racingCarDao.findBy(gameId);
gameResults.add(new GameResultDto(winners, racingCars));
}

return gameResults;
}
}
22 changes: 22 additions & 0 deletions src/main/java/racingcar/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package racingcar.view;

import racingcar.constant.ExceptionMessage;

import java.util.Scanner;

public class InputView {
private final Scanner scanner = new Scanner(System.in);

public String inputCarNames() {
return scanner.nextLine();
}

public int inputTryCount() {
try {
return scanner.nextInt();
} catch (Exception e) {
throw new IllegalArgumentException(
ExceptionMessage.TRY_COUNT_NOT_NUMBER_MESSAGE.getExceptionMessage());
}
}
}
Loading