Skip to content

Commit

Permalink
[1단계 - 웹 자동차 경주] 테오(최우성) 미션 제출합니다. (#63)
Browse files Browse the repository at this point in the history
* chore: 자동차 경주 미션 코드 가져오기

* docs: 페어 프로그래밍 룰 작성

* docs: 추가로 구현할 기능 목록 작성

* chore: 도메인 테스트 코드 가져오기

* feat: 자동차 경주 진행 요청을 보낼 시 응답한다.

* feat: 자동차 경주 진행에 대한 웹 요청을 받을 수 있다.

* feat: 사용자 입력이 잘못되었을 때 예외를 반환한다.

* feat: 자동차 경주 진행 결과에 대한 웹 응답을 전달할 수 있다.

* feat: 참여자들의 정보를 이동 거리 기준 내림차순으로 정렬한다.

* docs: 기능 구현 목록 추가

* feat: H2 의존성 추가 및 테이블 스키마 구현

* chore: 패키지 분리

* feat: 자동차 경주 게임 플레이 이력을 DB에 저장한다.

* test: 테스트 패키지 구조 변경

* refactor: 모든 Spring component에 @Autowired 애너테이션 명시

* chore: 개행 일관성 유지

* refactor: 변경 가능성이 없는 클래스, 파라미터에 final 키워드 부여

* fix: Repository 클래스를 확장 가능하게 변경

* refactor: DAO에서 쿼리 별 메소드 분리

* refactor: Service가 추상적인 DAO에 의존하도록 변경

* test: 테스트 코드의 웹 환경 설정 통일

* refactor: 자동차 위치정보의 불필요한 형변환 제거
  • Loading branch information
woosung1223 authored Apr 13, 2023
1 parent 07ad425 commit 2d18c57
Show file tree
Hide file tree
Showing 33 changed files with 1,087 additions and 13 deletions.
89 changes: 89 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,90 @@
# jwp-racingcar

# 페어 프로그래밍 룰

## 스위치 기준

- [x] 스위치 시간은 20분으로 한다.
- [x] 내비게이터는 전자기기에 손을 대지 않는다.

## 깃 컨벤션

- [x] 기능 목록에 있는 기능 단위로 커밋한다.
- [x] 작동할 수 있는 기능 단위로 커밋한다.
- [x] 커밋 메세지는 아래 키워드를 사용해 기능 목록 그대로 작성한다.
- feat: 기능 구현을 완료했을 때
- refactor: 기능의 변화 없이 코드를 변경했을 때
- test: 테스트 코드만 작성했을 때
- chore: 패키지 변경 등 사소한 수정사항이 생겼을 때
- fix: 프로그램의 결함을 수정할 때
- docs: 문서를 수정할 때

## 코드 컨벤션

- [x] 모든 클래스는 `final` 혹은 `abstract` 이어야 한다.
- [x] 모든 파라미터에 `final` 키워드를 붙인다.

## 구현 계획

- [x] 구현은 다음과 같은 순서로 진행된다.
1. Spring MVC 학습
2. 자동차 경주 미션 - 웹 요청/응답 구현하기
3. Spring JDBC 학습
4. 자동차 경주 미션 - DB 연동하기
5. 리팩토링

## 기타 룰

- [x] 미션 진행 2일차(수) 오후 4:00에 중간 회고를 진행한다.
- [x] 최소한 2시간에 한 번은 쉬어야 한다.
- [x] 커피챗을 최소 1회 진행한다.
- [x] 집중이 안된다면 페어에게 솔직하게 이야기한다.

---

# 추가로 구현할 기능 목록

## 웹 애플리케이션 구동

- [x] 자동차 경주 진행에 대한 웹 API를 구현한다.
- [x] 자동차 경주 진행에 대한 웹 요청을 받을 수 있다.
- [x] JSON 형태로 입력을 받는다.
- [x] 참여자들의 이름을 입력받는다.
- [x] 시도 횟수를 입력받는다.
- [x] `/plays``POST` 요청을 보낼 시 응답한다.
- [x] 자동차 경주 진행 결과에 대한 웹 응답을 전달할 수 있다.
- [x] JSON 형태로 전달한다.
- [x] 우승자들의 이름을 전달한다.
- [x] 참여자들의 정보를 전달한다.
- [x] 모든 참여자들의 이름을 전달한다.
- [x] 모든 참여자들의 이동 거리를 전달한다.
- [x] 이동 거리의 내림차순으로 정렬 후 전달한다.
- [x] 성공 시 STATUS CODE `200`를 반환한다.
- [x] 실패 시 다음과 같은 STATUS CODE를 반환한다.
- [x] 사용자 입력이 잘못되었을 때는 `400`을 반환한다.
- [x] 정의되지 않은 경로로 요청하는 경우 `404`를 반환한다.
- [x] 정의되지 않은 HTTP 메서드를 호출했을 때는 `405`를 반환한다.
- [x] 서버 내부에서 에러가 발생했을 때는 `500`을 반환한다.

## DB 연동

- [x] 자동차 경주 게임 플레이 이력을 DB에 저장한다.
- [x] H2 Database에 저장된다.
- [x] 저장되는 정보는 다음과 같다.
- [x] 플레이 횟수
- [x] 플레이어 별 최종 이동 거리(이름, 최종 위치)
- [x] 우승자
- [x] 플레이한 날짜/시간

# TO-STUDY

- [ ] `@Transactional` 학습, 어떻게 사용할 수 있는지
- [ ] Mock 테스트의 테스트 범위와 원리 학습하기
- [ ] RequestBody + Model 같이 사용할 수 있는지
- [ ] RequestBody의 required가 어떻게 동작하는지

# 고민사항

- [ ] 이너클래스로 `CarDTO`를 둘지, 외부 객체로 뺼지
- [ ] 어느 기준으로 DTO를 생성해야 할지
- [ ] Controller - Service, Service - Dao 사이마다 DTO를 새로 정의해야 하는지(필드가 새롭게 추가되는 경우)
19 changes: 12 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.9'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'java'
id 'org.springframework.boot' version '2.7.9'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

sourceCompatibility = '11'

repositories {
mavenCentral()
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:4.4.0'

implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
}

tasks.named('test') {
useJUnitPlatform()
useJUnitPlatform()
}
29 changes: 29 additions & 0 deletions src/main/java/racingcar/controller/RacingController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package racingcar.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import racingcar.dto.GameResultDto;
import racingcar.dto.RacingGameRequestDto;
import racingcar.service.RacingGameService;

import javax.validation.Valid;

@Controller
public final class RacingController {

private final RacingGameService racingGameService;

@Autowired
public RacingController(final RacingGameService racingGameService) {
this.racingGameService = racingGameService;
}

@PostMapping(path = "/plays")
@ResponseBody
public GameResultDto playRacingGame(@Valid @RequestBody final RacingGameRequestDto racingGameRequestDto) {
return racingGameService.playRacingGame(racingGameRequestDto);
}
}
41 changes: 41 additions & 0 deletions src/main/java/racingcar/dao/JdbcTemplateRacingGameDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package racingcar.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import racingcar.dto.CarDto;

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

@Repository
public class JdbcTemplateRacingGameDao implements RacingGameDao {

private final JdbcTemplate jdbcTemplate;

@Autowired
public JdbcTemplateRacingGameDao(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public Number saveGameResult(final String winners, final int trialCount) {
final String sqlToInsertGameResult = "INSERT INTO GAME_RESULT (winners, trial_count) values (?, ?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement preparedStatement = connection.prepareStatement(sqlToInsertGameResult, new String[]{"id"});
preparedStatement.setString(1, winners);
preparedStatement.setInt(2, trialCount);
return preparedStatement;
}, keyHolder);
return keyHolder.getKey();
}

public void savePlayerResults(final List<CarDto> racingCars, final Number gameResultKey) {
for (CarDto carDto : racingCars) {
String sqlToInsertPlayerResult = "INSERT INTO PLAYER_RESULT (name, position, game_result_id) values (?, ?, ?)";
jdbcTemplate.update(sqlToInsertPlayerResult, carDto.getName(), carDto.getPosition(), gameResultKey);
}
}
}
12 changes: 12 additions & 0 deletions src/main/java/racingcar/dao/RacingGameDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package racingcar.dao;

import racingcar.dto.CarDto;

import java.util.List;

public interface RacingGameDao {

Number saveGameResult(final String winners, final int trialCount);

void savePlayerResults(final List<CarDto> racingCars, final Number gameResultKey);
}
33 changes: 33 additions & 0 deletions src/main/java/racingcar/domain/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package racingcar.domain;

public class Car {

private final CarName carName;
private final Position position;

public Car(String name, int position) {
this.carName = new CarName(name);
this.position = new Position(position);
}

public void move() {
position.moveForward();
}

public int comparePosition(Car otherCar) {
return this.getPosition() - otherCar.getPosition();
}

public boolean hasSamePositionWith(Car otherCar) {
return comparePosition(otherCar) == 0;
}

public int getPosition() {
return position.getPosition();
}

public String getCarName() {
return carName.getCarName();
}
}

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

import racingcar.exception.CarNameBlankException;
import racingcar.exception.CarNameLengthException;

public class CarName {

private static final int MAX_CAR_NAME_LENGTH = 5;

private final String carName;

public CarName(String carName) {
validateCarName(carName);
this.carName = carName;
}

private void validateCarName(String carName) {
validateCarNameIsNotEmpty(carName);
validateCarNameLength(carName);
}

private void validateCarNameIsNotEmpty(String carName) {
if (carName.isBlank()) {
throw new CarNameBlankException();
}
}

private void validateCarNameLength(String carName) {
if (carName.length() > MAX_CAR_NAME_LENGTH) {
throw new CarNameLengthException();
}
}

public String getCarName() {
return carName;
}
}
20 changes: 20 additions & 0 deletions src/main/java/racingcar/domain/Coin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package racingcar.domain;

public class Coin {

private int remaining;

public Coin(int remaining) {
this.remaining = remaining;
}

public void use() {
if (isLeft()) {
remaining--;
}
}

public boolean isLeft() {
return remaining > 0;
}
}
6 changes: 6 additions & 0 deletions src/main/java/racingcar/domain/NumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package racingcar.domain;

public interface NumberGenerator {

int makeDigit();
}
31 changes: 31 additions & 0 deletions src/main/java/racingcar/domain/Position.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package racingcar.domain;

import racingcar.exception.PositionInvalidException;

public class Position {

private int position;

public Position(int position) {
validatePosition(position);
this.position = position;
}

private void validatePosition(int position) {
validatePositionIsNotNegative(position);
}

private void validatePositionIsNotNegative(int position) {
if (position < 0) {
throw new PositionInvalidException();
}
}

public void moveForward() {
position++;
}

public int getPosition() {
return position;
}
}
Loading

0 comments on commit 2d18c57

Please sign in to comment.