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

[1단계 - 웹 자동차 경주] 도기(김동호) 미션 제출합니다. #27

Merged
merged 24 commits into from
Apr 13, 2023

Conversation

kdkdhoho
Copy link

안녕하세요 조앤! 만나서 반갑습니다 ☺️
이번 웹 자동차 경주 미션, 열심히 해보겠습니다 !!

미션을 진행하며

  • 페어의 자동차 경주 코드를 이용했습니다.
  • 기존 자동차 경주 코드는 전혀 수정하지 않고, 웹 애플리케이션을 지원하기 위한 코드만 추가했습니다.

궁금한 점

1. 컨트롤러 -> 3개의 서비스 vs 컨트롤러 -> 서비스 -> 3개의 서비스

현재는 1개의 컨트롤러가 3개의 Service를 의존하는 구조를 가집니다.
하지만, 클래스 계층 구조를 설계함에 있어 RacingService라는 이름을 가지며 현재 컨트롤러의 로직을 수행하는 Service를 하나 만들고, RacingService가 3개의 Service를 의존하는 구조로 할지 고민이 됐습니다.
여러 정보를 찾아본 결과, Service간의 많은 의존은 유지보수가 힘들다는 정보를 찾았습니다.
추가로, 현재 Controller에 @Transactional을 붙여주어 원자성을 보장했습니다. 하지만 Controller에 @Transactinal이 붙어도 되는가? 에 대한 궁금증이 생겼습니다. 여러 정보를 찾아본 결과 Controller보다 Service 계층에서 사용하는 것이 권장된다는 것으로 파악했습니다. 그렇다면 컨트롤러 -> 서비스 -> 3개의 서비스의 구조를 가져야한다고 생각됩니다.

각 계층 구조별로 장단점이 존재하나요? 궁금해요!

Copy link

@seovalue seovalue left a comment

Choose a reason for hiding this comment

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

안녕하세요, 도기! 조앤이에요. 반가워요ㅎㅎ
1단계 잘 구현해주셨네요~ 몇가지 코멘트 남겼는데 확인 부탁드려요! 남겨주신 질문에 대해 저도 질문 남겼어요ㅎㅎ 답변 주시면 저도 그에 따른 답변 남겨볼게요~

README.md Outdated

- [x] 자동차 경주 코드 가져오기
- [x] 웹 요청/응답 구현하기
- [ ] DB 연동하기

Choose a reason for hiding this comment

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

완료된 것은 체크해주세요!

player_name varchar(30),
PRIMARY KEY (game_id, player_name),
FOREIGN KEY (game_id) REFERENCES game (id),
FOREIGN KEY (player_name) REFERENCES player (name)

Choose a reason for hiding this comment

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

단순 name만을 갖는 player라는 데이터는 어떤 이유에서 필요하다고 생각하셨나요?
record는 fk를 두 개 가져요. fk를 가지면 어떤 장/단점이 있을까요?
복합키로 pk를 구성해주신 이유가 궁금하고, 복합키이자 PK가 모두 다른 테이블의 PK와 fk를 갖도록 설계해주신 이유가 무엇인가요?

Copy link
Author

Choose a reason for hiding this comment

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

단순 name만을 갖는 player라는 테이블이 필요하다고 생각한 이유

처음 테이블 설계할 때 아래와 같은 구조로 설계했습니다.

CREATE TABLE game (
    id, trial_count, date
)

CREATE TABLE result (
    game_id, player_name, isWinner, position
)

하지만, 이럴 경우 1개의 game row에 n명의 player 수만큼 result row가 생기는 것과 같은 이름으로 반복해서 플레이한다면 같은 이름이 계속해서 생기는 것이 맘에 들지 않다고 의견을 제시했습니다.
그래서 현재와 같은 구조를 가지게 되었습니다. 하지만 페어와 함께 이야기해보니 중복을 제거하기 위해 만든 player 테이블이지만, 결국 result 테이블 내에 player_name이 중복이 발생한다는 것을 발견했습니다. 따라서 player 테이블을 제거하는 방향으로 수정하겠습니다.

fk를 가지면 어떤 장/단점이 있을까?

장점

fk를 사용하지 않는다면, 같은 값의 데이터임에도 같은 값이라는 것을 보장해줄 수 없습니다.
그래서 fk를 통해 이 문제를 해결할 수 있다고 생각합니다.

단점

테이블의 수가 많아져서 관리 포인트가 많아짐과 동시에 테이블 의존성이 생깁니다.

복합키를 pk로 구성한 이유

result 테이블에서 각각의 행을 구별하려면 적어도 game_id와 player_name을 통해 비교할 수 있다고 판단했기 때문입니다.

import racingcar.service.PlayerService;
import racingcar.service.RecordService;

@RestController

Choose a reason for hiding this comment

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

RestController와 Controller는 어떤 차이를 가질까요?

Copy link
Author

@kdkdhoho kdkdhoho Apr 13, 2023

Choose a reason for hiding this comment

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

  • @RestContorller
    @Controller@ResponseBody 어노테이션을 합친 것으로, 주로 데이터를 json 등의 형태로 http body부에 실어서 반환하기 위해 사용하는 것으로 알고 있습니다.

  • @Controller
    단지 view를 반환하기 위해 사용하는 것으로 알고 있습니다. 여기서 view란 html 파일을 의미합니다.


@PostMapping("/plays")
@Transactional
public PlayResponse plays(@RequestBody final PlayRequest playRequest) {

Choose a reason for hiding this comment

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

requestBody를 DTO로 설계해주신 이유가 무엇인가요?
String names, int count로 받는 것과 어떤 차이 혹은 장단점이 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

미션 상, 요청의 형태는 아래와 같습니다.

{
    "names": "브리,토미,브라운",
    "count": 10
}

위 요청에 대한 응답을 받기 위해 PlayRequest 라는 MapperObject를 설계하였고, 이를 Dto로 사용했습니다.

현재 HTTP 요청의 형태는 한 덩어리의 json으로 묶어져 오기 때문에 말씀해주신 String names, int count 형태로 받는 것은 불가능합니다.
혹시 제가 놓친 부분이 있을까요?


MapperObject로 받는 것과 String names, int count로 받는 것의 차이로는 다음과 같습니다.

  • 만약 여러 Controller의 여러 메서드에서 String names, int count 처럼 받다가, 수정이 필요한 경우 해당 값들을 사용하는 메서드들을 모두 수정해줘야 합니다. 하지만 MapperObject로 받는다면 객체만 수정해주면 됩니다.

Choose a reason for hiding this comment

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

👍
@RequestBody는 하나에 대해서만 사용할 수 있어요. 따라서 아래와 같은 형태로 사용하게 된다면 names를 받으면서 request body 스트림을 닫아버리기 때문에 다음 값인 count를 받을 수 없어요.

    @PostMapping("/plays")
    public PlayResponse plays(
            @RequestBody String names,
            @RequestBody Integer count
    ) {
        return gameService.playRacing(names, count);
    }

만약 받아야하는 값이 하나만 있다면, 도기는 그래도 DTO를 사용하실 건가요, 아니면 그냥 @RequestBody String names와 같이 단순 wrapper value를 사용해주실 건가요?

Copy link
Author

Choose a reason for hiding this comment

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

하나만 있다면 후자를 선택할 것 같습니다.
추후에 여러 값을 받아야 하는 상황이 온다거나, 유지 보수를 위해 xxxRequest 처럼 명시해줘야 한다면 DTO를 사용할 것 같습니다.

this.carNameValidator = carNameValidator;
}

public Cars createCars(final PlayRequest playRequest) {

Choose a reason for hiding this comment

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

외부로부터 들어오는 요청을 service까지 진입하도록 해주신 이유가 무엇인가요?

Copy link
Author

Choose a reason for hiding this comment

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

사실 별 생각없이 사용했던 것 같습니다 😅
외부 요청이 Service까지 진입한다면 수정 할 포인트가 늘어나는 문제가 생길 것 같아요.

생각해본 결과, 아래와 같은 방법들이 적절할 것 같아요.

  1. 외부 요청을 Controller에서 받아 적절히 새로운 Dto로 변환하여 Service로 전달
  2. Service에서 필요한 데이터만을 메서드 시그니처로 선언하여 Controller에서 필요한 값만을 전달

여기서 새로운 Dto를 생성하는 것은 비용이 과하게 생성된다고 판단하여 2번 방식으로 수정하려고 합니다.

Choose a reason for hiding this comment

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

오홍! 여러 가지를 생각해보신 것이 인상깊네요ㅎㅎ Spring 미션을 진행하시면서 다양한 방법을 시도해보셨음 해요. Request를 서비스까지 넘겼을 때, 서비스용 DTO를 분리해서 매 요청바다 request.toDto()를 했을 때, 매 Request를 풀어 헤쳐 메서드 시그니처에 파라미터로 반영했을 때.. 여러가지를 경험해보시고 도기만의 인사이트를 얻으시길 바랍니다!

@@ -0,0 +1,20 @@
package racingcar.model;

Choose a reason for hiding this comment

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

패키지명을 모델로 명명해주신 이유가 궁금해요!

Copy link
Author

Choose a reason for hiding this comment

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

페어의 코드를 가져오다 보니 이렇게 됐습니다.

그런데 이 부분에 대해 질문이 생겼습니다.

 - domain
    ㄴ model (Entity 등)
    ㄴ service (service 계층)

위와 같은 구조로 수정해도 되는지 궁금합니다.

Choose a reason for hiding this comment

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

service는 도메인일까요? 👀
엔티티는 모델일까요?ㅎㅎ

Copy link
Author

Choose a reason for hiding this comment

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

용어에 대해 먼저 정리를 해야될 것 같아서 각 용어에 대한 제 생각을 적어보겠습니다.

  • domain: 유저가 프로그램을 사용하는 궁극적인 목적과 관련된 주제 및 객체
  • service: Service Layer를 의미하며, 도메인 로직과 서비스 로직을 처리하는 역할을 맡은 객체
  • 엔티티: 데이터 모델링에서 사용되는 객체
  • 모델: 잘 모르겠습니다..

용어를 정리하고 보니 모두 다른 개념들이라고 생각됩니다. 따라서 domain, service, entity 처럼 각각 나누는 것이 좋다고 생각되네요!

Choose a reason for hiding this comment

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

👍
저는 모델은 주로 엔티티를 Response로 변환하여 외부로 반환할 때, 중간 처리가 꼭 필요한 경우가 있어요. 엔티티에서 특정 필드만을 내보내야한다던지, 특정 필드만을 가공해야한다던지? 그런 경우 가끔 사용하는 것 같아요!


@PostMapping("/plays")
@Transactional
public PlayResponse plays(@RequestBody final PlayRequest playRequest) {

Choose a reason for hiding this comment

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

스크린샷 2023-04-12 오후 11 47 00

의도된 동작일까요?

@@ -1 +1,7 @@
# jwp-racingcar

# 프로그램 요구사항

Choose a reason for hiding this comment

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

질문에 대한 이야기 여기서 해볼까요~

답변을 드리기 전에, 우선 질문을 먼저 드릴게요.

  1. 3개의 서비스로 구분해주신 이유가 궁금해요.
  2. 도기가 생각하시는 서비스의 역할은 무엇인가요?
  3. 그럼 도기가 생각하시는 컨트롤러의 역할은 무엇인가요?

  1. Transaction은 어떤 범위를 가져야할까요?
  2. Transactional 애노테이션은 왜 서비스에 붙이기가 권장될까요?
  3. 지금 구현해주신 프로그램에서 Transactional 애노테이션을 붙이지 않으면 어떻게 동작하나요?

Copy link
Author

@kdkdhoho kdkdhoho Apr 13, 2023

Choose a reason for hiding this comment

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

3개의 서비스로 구분한 이유

각 Dao에 접근하는 각각의 Service를 만들어주기 위해 3개의 서비스(Player, Game, ResultService)로 구분했습니다.

서비스의 역할

비즈니스 로직과 서비스 로직을 처리하는 역할이라고 생각합니다.

컨트롤러의 역할

client가 이용할 API로서, 사용자의 요청을 받아 적절한 로직을 수행하도록 하고, 결과를 반환합니다.


Transaction은 어떤 범위를 가져야 할까?

사용자의 요청에 따른 로직의 시작과 끝까지 가져야 한다고 생각합니다.

@Transactional은 왜 서비스에 붙이기 권장될까?

Service 계층에서 Dao를 통해 데이터를 조작하는 위치이기 때문이지 않을까 생각됩니다.
적고 보니, WebController.plays(PlayRequest) 내에 있는 로직들을 임의의 Service 계층에게 위임하여 해당 메서드에서 모든 로직을 처리하도록 하는 것이 더 좋다고 생각됩니다.

지금 구현한 프로그램에서 @Transactional을 붙이지 않으면?

만약 gameService.play()가 실행되는 도중에 예외가 발생한다면, game은 생성되었지만 result는 저장이 되지 않게 됩니다.
그렇기 때문에 데이터 무결성을 보장하지 못합니다.

Choose a reason for hiding this comment

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

  1. 컨트롤러 -> 3개의 서비스 vs 컨트롤러 -> 서비스 -> 3개의 서비스
    현재는 1개의 컨트롤러가 3개의 Service를 의존하는 구조를 가집니다.
    하지만, 클래스 계층 구조를 설계함에 있어 RacingService라는 이름을 가지며 현재 컨트롤러의 로직을 수행하는 Service를 하나 만들고, RacingService가 3개의 Service를 의존하는 구조로 할지 고민이 됐습니다.
    여러 정보를 찾아본 결과, Service간의 많은 의존은 유지보수가 힘들다는 정보를 찾았습니다.
    추가로, 현재 Controller에 @transactional을 붙여주어 원자성을 보장했습니다. 하지만 Controller에 @Transactinal이 붙어도 되는가? 에 대한 궁금증이 생겼습니다. 여러 정보를 찾아본 결과 Controller보다 Service 계층에서 사용하는 것이 권장된다는 것으로 파악했습니다. 그렇다면 컨트롤러 -> 서비스 -> 3개의 서비스의 구조를 가져야한다고 생각됩니다.

각 계층 구조별로 장단점이 존재하나요? 궁금해요!

답변 남겨주셔서 감사해요! 저도 도기의 의견을 바탕으로 남겨주신 질문에 대해 답을 달아볼게요~
우선 제 생각으로는 @Transactional은 서비스에 있는게 맞다는 생각이 들어요. 트랜잭션이라는 것은 데이터의 변경을 감당한다고 여겨지는데, 말씀해주신 것처럼 서비스는 비즈니스 로직을 관장하게 되고, 비즈니스 로직 안에서 데이터의 변경이 잦게 일어나요. 만약 컨트롤러에 트랜잭셔널이 붙어있다면 컨트롤러에서부터 데이터의 변경을 허용 한다는 의미로 여겨질 것 같아요.

그리고 dao마다 서비스는 꼭 있을 필요는 없다고 생각해요. dao들이 하는 역할과 책임을 관장하는 한 가지 핵심 로직이 있다면 그것은 하나의 서비스에서 행해도 된다고 저는 생각합니다!

하나의 컨트롤러가 세개의 서비스를 갖는 것과 서비스가 Facade의 역할로 3개의 서비스를 갖는 것은 결국 같다고 생각해요. 제 생각엔 서비스가 3개까지 필요하지 않고, 핵심 비즈니스 로직인 자동차 게임을 실행한다.를 다루는 서비스 하나로 모두 해결할 수 있다고 생각해요!!!

그리고 계층 구조별로 장단점은 계층 구조를 왜 나누게 됐는지를 고민해보시면 좋을 것 같아요. 모든 로직을 Controller 하나에서 다 처리할 수 있는데, 우리는 왜 service와 dao를 구분했고 순차적으로 책임을 나눠 처리하게 했는지..ㅎㅎ

Comment on lines 32 to 38
Cars cars = playerService.createCars(playRequest);
playerService.saveCars(cars);

long gameId = gameService.saveGame(playRequest);
gameService.play(playRequest, cars);

return recordService.result(gameId, cars);

Choose a reason for hiding this comment

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

이 순서대로 보자면 프로그램은 이렇게 동작한다고 여겨져요.

  1. player서비스에서 car를 생성한다.
  2. 생성한 car를 player 서비스에서 저장한다.
  3. 요청을 바탕으로 game을 저장한다.
  4. 요청과 1에서 만든 car를 바탕으로 게임을 play한다.
  5. 앞서 저장한 gameId와 1에서 만든 car를 기록서비스에서 result한다?

자동차 경주 게임에서 의도한 비즈니스 로직의 흐름이 맞을까요?

Copy link
Author

@kdkdhoho kdkdhoho Apr 13, 2023

Choose a reason for hiding this comment

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

죄송하지만 어떤 점을 의도하고 리뷰를 남겨주셨는지 파악이 안됩니다 😭
혹시 다른 힌트를 주실 수 있으실까요?

앗 혹시 5. 앞서 저장한 gameId와 1에서 만든 car를 기록서비스에서 result한다? 에서 **result한다?**가 포인트일까요??
이 부분이 맞다면, 게임 play -> 게임 결과 저장 -> 게임 결과 반환 의 흐름으로 수정하였습니다!

Choose a reason for hiding this comment

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

아하!ㅎㅎ 좀 헷갈렸을 수 있을 것 같네요.
제 생각에 자동차 게임의 핵심 비즈니스 로직은 Car 서비스에서 Car를 생성하고, 그 Car로 Game을 play하고, 그 결과값을 반환한다. 로 생각돼요. 그런데 기존의 비즈니스 로직은 서비스와 역할이 모호했던 것 같아서 말씀드렸어요!

Copy link
Author

Choose a reason for hiding this comment

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

아직 비즈니스 로직과 서비스를 구분하는 능력이 많이 부족한 것 같아요🥹

그런데 그렇다면 Car를 생성하기 위한 CarService가 따로 존재해야 하나요?
Car를 생성하는 것은 Cars나 Car에게 위임해서 GameService에서 처리하면 안되나요??

Choose a reason for hiding this comment

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

        Cars cars = playerService.createCars(playRequest);
        playerService.saveCars(cars);

        long gameId = gameService.saveGame(playRequest);
        gameService.play(playRequest, cars);

        return recordService.result(gameId, cars);

Car 생성에 대한 것을 Cars나 Car에 위임해서 처리해도 될 것 같아요. 제가 어색했다고 느껴졌던 부분은, playerService에서 차를 생성하고 저장한다는게 어색했던 것 같아요. 말씀해주신 대로 gameService의 play 메서드 내부에서 한꺼번에 처리해줘도 좋을 것 같아요!

import java.util.stream.Collectors;

@Service
public class RecordService {

Choose a reason for hiding this comment

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

각 서비스들이 정확히 어떤 책임과 역할을 갖는걸까요?

Copy link
Author

Choose a reason for hiding this comment

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

PlayerService는 사용자로부터 입력된 자동차 이름을 저장하고, Cars 객체로 변환해주는 역할을 가집니다.
GameService는 Game 데이터를 저장하고, 자동차 경주 게임을 실행하는 역할을 가집니다.
RecordService는 자동차 경주 결과를 저장하고, 사용자에게 반환할 응답을 생성하는 역할을 가집니다.

Choose a reason for hiding this comment

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

왜 자동차 이름과 Cars를 변환하는 역할을 Player 서비스가 가지나요?

Copy link
Author

Choose a reason for hiding this comment

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

Player를 Car로 의미하고 네이밍했습니다. 지금 보니 네이밍이 너무 별로군요 🤔
하지만 지금은 PlayerService를 제거했으니 안심하셔도 됩니다!

Copy link

@seovalue seovalue left a comment

Choose a reason for hiding this comment

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

안녕하세요, 도기! 조앤이에요ㅎㅎ
변경해주신 것 확인했어요. 1단계 요구사항은 만족하는 것 같아 다음 단계 진행해주시면 좋을 것 같아요!
추가로 이전에 남긴 질문들에 대해 저도 코멘트 달아보았는데요, 이번에 남긴 리뷰와 함께 이전 것도 꼭 확인해주시면 좋을 것 같아요~ 👍

@@ -4,4 +4,7 @@

- [x] 자동차 경주 코드 가져오기
- [x] 웹 요청/응답 구현하기
- [ ] DB 연동하기
- [x] 사용자가 index 페이지에서 자동차 이름과 시도 횟수 정보를 포함한 요청을 보낸다.

Choose a reason for hiding this comment

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

👍

package racingcar.model;

public class TrialCount {

Choose a reason for hiding this comment

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

👍 검증을 위한 객체 생성 좋네요.
지금 DTO로 Request를 Controller를 통해 전달받고 있는데, @Valid 라는 어노테이션을 통해 Request의 값을 검증해볼 수도 있어요ㅎㅎ 참고차 남겨요. https://jyami.tistory.com/55

private static final String DELIMITER = ",";

private final CarNameValidator carNameValidator;
private final RecordService recordService;
private final GameDao gameDao;

@Autowired

Choose a reason for hiding this comment

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

오호! 여기에 @Autowired를 명시해주신 이유가 무엇인가요!

Copy link
Author

@kdkdhoho kdkdhoho Apr 14, 2023

Choose a reason for hiding this comment

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

아직 Spring Core를 공부하지 않아 정확히는 모르겠습니다.
하지만 스프링 빈 컨테이너가 알아서 객체들을 싱글톤으로 관리해주는 것으로 알고 있고, 생성자에 @Autowired를 붙여주어 스프링 빈을 자동 주입받기 위해 사용했습니다.

찾아보니 생성자가 1개라면 스프링이 붙여준다고 하네요 😅
그럼에도 저는 붙여줄 생각입니다! 왜냐하면 코드의 일관성을 쉽게 지킬 수 있다고 생각합니다. 또, 만약 위 사실을 모르는 개발자와 협업한다면 명시적으로 적어주는 게 해당 개발자를 배려하는 것으로 생각하기 때문이에요!

조앤은 생성자가 1개 있을 때 @Autowired를 붙여주나요? 🤔

Choose a reason for hiding this comment

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

위에서도 댓글을 남겨두었는데요, 팀의 컨벤션에 주로 맞추는 편이에요!

Comment on lines +33 to +34
List<String> winnerNames = findWinnerName(cars);
List<VehicleDto> racingCars = toVehicleDto(cars);

Choose a reason for hiding this comment

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

DTO를 사용해주실 때와, 원시값을 사용해주실 때는 어떤 기준이 있으신가요?

Copy link
Author

Choose a reason for hiding this comment

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

위 코드에서는 어떠한 기준에 따라 선택하지는 않았습니다.
단지, Response 객체가 Service에서 Controller로 전달될 때, 데이터에 도메인이 포함되어 있으면 Controller에서 잘못 사용될 수도 있기 때문에 이를 방지하고자 적절히 타입을 변환했습니다.
따라서 우승자의 이름만이 필요하기에 List<String>, 참가한 모든 자동차들의 이름과 거리만을 필요하기에 List<VehicleDto>로 변환했던 것 같아요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants