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

[FEAT] 타임라인 선수 교체 정보 관련 기능 추가 #116

Merged
merged 17 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.sports.server.query.application;

import com.sports.server.command.game.domain.GameTeam;
import com.sports.server.command.record.domain.ScoreRecord;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static java.util.stream.Collectors.toMap;

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class ScoreHistory {

private final Map<ScoreRecord, ScoreSnapshot> snapshots;

public static ScoreHistory of(List<ScoreRecord> scoreRecords, List<GameTeam> gameTeams) {
Map<ScoreRecord, ScoreSnapshot> values = new HashMap<>();
Map<GameTeam, Integer> scores = initializeScores(gameTeams);
for (ScoreRecord record : scoreRecords) {
applyScore(scores, record);
ScoreSnapshot snapshot = generateSnapshot(scores, gameTeams);
values.put(record, snapshot);
}
return new ScoreHistory(values);
}

private static Map<GameTeam, Integer> initializeScores(List<GameTeam> gameTeams) {
return gameTeams.stream()
.collect(toMap(gameTeam -> gameTeam, gameTeam -> 0));
}

private static void applyScore(Map<GameTeam, Integer> scores, ScoreRecord record) {
GameTeam gameTeam = record.getRecord().getGameTeam();
int score = record.getScore();
scores.put(gameTeam, scores.get(gameTeam) + score);
}

private static ScoreSnapshot generateSnapshot(Map<GameTeam, Integer> scores,
List<GameTeam> gameTeams) {
Map<GameTeam, Integer> snapshot = new HashMap<>();
gameTeams.forEach(team -> snapshot.put(team, scores.get(team)));
return new ScoreSnapshot(snapshot);
}

public List<ScoreRecord> getScoreRecordsOrderByTimeDesc() {
return snapshots.keySet().stream()
.sorted((r1, r2) -> Integer.compare(r2.getRecord().getRecordedAt(), r1.getRecord().getRecordedAt()))
.toList();
}

public ScoreSnapshot getSnapshot(ScoreRecord scoreRecord) {
return snapshots.get(scoreRecord);
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
package com.sports.server.query.application;

import com.sports.server.command.game.domain.GameTeam;
import com.sports.server.command.record.domain.ScoreRecord;
import com.sports.server.query.dto.mapper.RecordMapper;
import com.sports.server.query.dto.response.RecordResponse;
import com.sports.server.query.dto.response.ScoreRecordResponse;
import com.sports.server.query.repository.GameTeamQueryRepository;
import com.sports.server.query.repository.ScoreRecordQueryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

@Service
@Transactional(readOnly = true)
Expand All @@ -24,51 +17,17 @@ public class ScoreRecordQueryService implements RecordQueryService {

private final ScoreRecordQueryRepository scoreRecordQueryRepository;
private final GameTeamQueryRepository gameTeamQueryRepository;
private final RecordMapper recordMapper;

@Override
public List<RecordResponse> findByGameId(Long gameId) {
List<GameTeam> gameTeams = gameTeamQueryRepository.findAllByGameWithTeam(gameId);
List<ScoreRecord> scoreRecords = scoreRecordQueryRepository.findByGameId(gameId);
return mapToResponses(scoreRecords, gameTeams);
}

private List<RecordResponse> mapToResponses(List<ScoreRecord> scoreRecords,
List<GameTeam> gameTeams) {
Map<GameTeam, Integer> scores = initializeScores(gameTeams);
List<RecordResponse> responses = scoreRecords.stream()
.map(record -> mapToResponse(gameTeams, scores, record))
.collect(toList());
Collections.reverse(responses);
return responses;
}

private Map<GameTeam, Integer> initializeScores(List<GameTeam> gameTeams) {
return gameTeams.stream()
.collect(toMap(gameTeam -> gameTeam, gameTeam -> 0));
}

private RecordResponse mapToResponse(List<GameTeam> gameTeams,
Map<GameTeam, Integer> scores,
ScoreRecord scoreRecord) {
GameTeam gameTeam = scoreRecord.getRecord().getGameTeam();
int score = scoreRecord.getScore();

scores.put(gameTeam, scores.get(gameTeam) + score);

List<ScoreRecordResponse.History> histories = generateHistories(scores, gameTeams);
return RecordResponse.from(
scoreRecord,
new ScoreRecordResponse(score, histories)
ScoreHistory scoreHistory = ScoreHistory.of(
scoreRecordQueryRepository.findByGameId(gameId),
gameTeamQueryRepository.findAllByGameWithTeam(gameId)
);
}

private List<ScoreRecordResponse.History> generateHistories(Map<GameTeam, Integer> scores,
List<GameTeam> gameTeams) {
return gameTeams.stream()
.map(team -> new ScoreRecordResponse.History(
team.getLeagueTeam().getName(),
team.getLeagueTeam().getLogoImageUrl(),
scores.get(team)))
return scoreHistory.getScoreRecordsOrderByTimeDesc()
.stream()
.map(record -> recordMapper.toRecordResponse(record, scoreHistory.getSnapshot(record)))
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.sports.server.query.application;

import com.sports.server.command.game.domain.GameTeam;

import java.util.Comparator;
import java.util.List;
import java.util.Map;


public class ScoreSnapshot {

private final Map<GameTeam, Integer> values;

public ScoreSnapshot(Map<GameTeam, Integer> values) {
this.values = values;
}

public Integer getScore(GameTeam team) {
return values.get(team);
}

public List<GameTeam> getTeamsOrderById() {
return values.keySet()
.stream()
.sorted(Comparator.comparingLong(GameTeam::getId))
.toList();
}
}
41 changes: 41 additions & 0 deletions src/main/java/com/sports/server/query/dto/mapper/RecordMapper.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package com.sports.server.query.dto.mapper;

import com.sports.server.command.game.domain.GameTeam;
import com.sports.server.command.leagueteam.LeagueTeam;
import com.sports.server.command.record.domain.Record;
import com.sports.server.command.record.domain.ReplacementRecord;
import com.sports.server.command.record.domain.ScoreRecord;
import com.sports.server.query.application.ScoreSnapshot;
import com.sports.server.query.dto.response.RecordResponse;
import com.sports.server.query.dto.response.ReplacementRecordResponse;
import com.sports.server.query.dto.response.ScoreRecordResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;


@Component
@Transactional(readOnly = true)
Expand All @@ -29,4 +35,39 @@ public RecordResponse toRecordResponse(ReplacementRecord replacementRecord) {
new ReplacementRecordResponse(replacementRecord.getReplacedLineupPlayer().getName())
);
}

public RecordResponse toRecordResponse(ScoreRecord scoreRecord, ScoreSnapshot snapshot) {
Record record = scoreRecord.getRecord();
int score = scoreRecord.getScore();
LeagueTeam team = record.getGameTeam().getLeagueTeam();

List<ScoreRecordResponse.Snapshot> histories = toHistoryResponses(snapshot);
return new RecordResponse(
record.getRecordedQuarter(),
record.getRecordType().name(),
record.getRecordedAt(),
scoreRecord.getLineupPlayer().getName(),
team.getName(),
team.getLogoImageUrl(),
new ScoreRecordResponse(score, histories),
null
);
}

private List<ScoreRecordResponse.Snapshot> toHistoryResponses(ScoreSnapshot snapshot) {
return snapshot.getTeamsOrderById()
.stream()
.map(gameTeam -> toHistoryResponse(snapshot, gameTeam))
.toList();
}

private ScoreRecordResponse.Snapshot toHistoryResponse(ScoreSnapshot snapshot,
GameTeam gameTeam) {
LeagueTeam leagueTeam = gameTeam.getLeagueTeam();
return new ScoreRecordResponse.Snapshot(
leagueTeam.getName(),
leagueTeam.getLogoImageUrl(),
snapshot.getScore(gameTeam)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

public record ScoreRecordResponse(
Integer point,
Copy link
Contributor

Choose a reason for hiding this comment

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

저는 개인적으로 point 가 조금 어색하다고 느껴지는 것 같아요. 왜냐하면 이전까지 이러한 득점의 경우 score 를 썼었고 현재 score_records 테이블에서도 득점 정보에 관해서 score 라는 컬럼을 쓰고 있어서인 것 같아요!

작성해주신 코드에서의 score 의 역할은 Snapshot 에 종속돼서 과거의 점수를 서술하는 용어라면, 테이블에서의 score 은 득점하는 점수 자체를 서술하는 용어라고 다가와요. 이에 대해서 어떻게 생각하시는지 궁금해요! 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

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

[
    {
        "gameQuarter": "후반전",
        "records": [
            {
                "type": "SCORE",
                "recordedAt": 26,
                "playerName": "진승희",
                "teamName": "팀 A",
                "teamImageUrl": "...",
                "score": {
                     "point": 1
                     "snapshot": [
                         {"teamName": "...", "score": 1, "teamImageUrl": "..."},
                         {"teamNamere": "...", "score": 1, "teamImageUrl": "..."},
                     ]
                }
                "replacement": null // SCORE 타입일 땐 null
...

위 스펙에서 각 타입에 따라 null인 객체를 하나로 묶기 위해 scorereplacement라는 이름으로 각 타입에 맞는 특수한 값들을 묶어줬어요. 이 구조에서 득점을 score라고 하자니 records.score.score가 되기에 어색하다고 느껴졌어요.

score 하위의 몇점을 득점했는지에 의미를 담아 point라고 이름을 명명했는데 더 나은 네이밍이 있을까요? 아니면 최상위 객체의 이름 (score, replacment)를 다르게 표기해야 할까요?

Copy link
Contributor

@Jin409 Jin409 Feb 26, 2024

Choose a reason for hiding this comment

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

현재 테이블명을 따라서 scoreRecords, ReplacementRecord 로 하는건 어떨까요?
사실상 타입이 score 이고 replacement 인거고, 그 각자는 각각 scoreRecord, replacementRecord 이기는 하니까요!
point 는 snapshot 내부에서는 같은 맥락임에도 score 로 표기되고 있기도 하고, 저희가 사전에 정의되었거나 논의된 용어가 아니라서 나중에도, api 스펙 상에서도 혼돈을 줄 것 같다는 생각이 들어요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Jin409
리뷰 반영햇습니다~

Copy link
Contributor

Choose a reason for hiding this comment

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

수고하셨습니당~ 머지해도 좋을 것 같아요!

List<History> histories
List<Snapshot> snapshot
) {

public record History(
public record Snapshot(
String teamName,
String teamImageUrl,
Integer score
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ public class TimelineQueryAcceptanceTest extends AcceptanceTest {
TEAM_B,
TEAM_B_IMAGE_URL,
new ScoreRecordResponse(3, List.of(
new ScoreRecordResponse.History(
new ScoreRecordResponse.Snapshot(
TEAM_A, TEAM_A_IMAGE_URL, 2),
new ScoreRecordResponse.History(
new ScoreRecordResponse.Snapshot(
TEAM_B, TEAM_B_IMAGE_URL, 3)
)),
null
Expand Down Expand Up @@ -95,9 +95,9 @@ public class TimelineQueryAcceptanceTest extends AcceptanceTest {
TEAM_A,
TEAM_A_IMAGE_URL,
new ScoreRecordResponse(2, List.of(
new ScoreRecordResponse.History(
new ScoreRecordResponse.Snapshot(
TEAM_A, TEAM_A_IMAGE_URL, 2),
new ScoreRecordResponse.History(
new ScoreRecordResponse.Snapshot(
TEAM_B, TEAM_B_IMAGE_URL, 0)
)),
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ public class TimelineQueryControllerTest extends DocumentationTest {
TEAM_B,
TEAM_B_IMAGE_URL,
new ScoreRecordResponse(3, List.of(
new ScoreRecordResponse.History(
new ScoreRecordResponse.Snapshot(
TEAM_A, TEAM_A_IMAGE_URL, 2),
new ScoreRecordResponse.History(
new ScoreRecordResponse.Snapshot(
TEAM_B, TEAM_B_IMAGE_URL, 3)
)),
new ReplacementRecordResponse("선수3")
Expand All @@ -62,9 +62,9 @@ public class TimelineQueryControllerTest extends DocumentationTest {
TEAM_A,
TEAM_A_IMAGE_URL,
new ScoreRecordResponse(2, List.of(
new ScoreRecordResponse.History(
new ScoreRecordResponse.Snapshot(
TEAM_A, TEAM_A_IMAGE_URL, 2),
new ScoreRecordResponse.History(
new ScoreRecordResponse.Snapshot(
TEAM_B, TEAM_B_IMAGE_URL, 0)
)),
new ReplacementRecordResponse("선수3")
Expand All @@ -91,12 +91,12 @@ public class TimelineQueryControllerTest extends DocumentationTest {
fieldWithPath("[].records[].teamName").type(JsonFieldType.STRING).description("기록의 대상 팀 이름"),
fieldWithPath("[].records[].teamImageUrl").type(JsonFieldType.STRING).description("기록의 대상 팀 이미지"),
fieldWithPath("[].records[].score.point").type(JsonFieldType.NUMBER).description("SCORE 타입일 때 득점한 점수"),
fieldWithPath("[].records[].score.histories[].teamName").type(JsonFieldType.STRING)
.description("SCORE 타입일 때 점수 히스토리에 표시할 팀 이름"),
fieldWithPath("[].records[].score.histories[].teamImageUrl").type(JsonFieldType.STRING)
.description("SCORE 타입일 때 점수 히스토리에 표시할 팀 이미지"),
fieldWithPath("[].records[].score.histories[].score").type(JsonFieldType.NUMBER)
.description("SCORE 타입일 때 점수 히스토리에 표시할 점수"),
fieldWithPath("[].records[].score.snapshot[].teamName").type(JsonFieldType.STRING)
.description("SCORE 타입일 때 점수 스냅샷에 표시할 팀 이름"),
fieldWithPath("[].records[].score.snapshot[].teamImageUrl").type(JsonFieldType.STRING)
.description("SCORE 타입일 때 점수 스냅샷에 표시할 팀 이미지"),
fieldWithPath("[].records[].score.snapshot[].score").type(JsonFieldType.NUMBER)
.description("SCORE 타입일 때 점수 스냅샷에 표시할 점수"),
fieldWithPath("[].records[].replacement.replacedPlayerName").type(JsonFieldType.STRING)
.description("REPLACEMENT 타입일 때 교체되어 IN 되는 선수")
)
Expand Down