축구팀 관리 프로젝트 15일차 - 경기 후 기록 등록하는 로직
오늘 한 일은 경기 후 기록 등록하는 저장 로직을 수정했습니다. 기존 저장 방식을 다시 확인해보니 중복되는 부분이 있었습니다. 그래서 수정하는 작업을 하였고, 원래는 이 작업 끝나고 못다한 프론트 작업을 하려했으나 생각보다 이 수정하는 부분에 시간을 쓰게 되었습니다.
경기 후 기록 등록 수정
두 팀의 축구 경기가 끝나면 기록을 등록할 수 있습니다. 기록에는 팀별 기록, 선수별 기록 두가지가 있습니다. 순서는 그림처럼 팀 결과를 등록하고, 그 후 선수별 결과 기록을 할 수 있도록 기획하였습니다.
기존 팀 결과 기록시 API 요청을 위해 받는 body 값 중에서 득점, 어시스트 같은 값들을 다음과 같이 받고 있었습니다.
"goals": "[{'playerId':1,'count':2},{'playerId':2,'count':1}]"
"assists": "[{'playerId':3,'count':2},{'playerId':2,'count':1}]",
문제는 그러고 나서 선수별 결과 기록할 때도 위 값을 넣고 있었다는 점입니다.
"assists": 3,
"goals": 1,
선수별 결과 기록은 선수 개개인의 평가 기록을 하는 과정이고, 팀 결과 기록은 팀 전체에 대한 결과입니다.위 항목은 선수별 결과에서 합산을 내어 팀결과에 넣어도 되는 항목인데 굳이 팀 결과에서도 따로 입력할 필요가 없어보였습니다. 사이트 사용자 입장에서도 개인별로 골과 어시스트 수를 입력하는데 팀결과에 또 적는 것은 불필요한 입력을 늘리는 것이라 판단했습니다.
그래서 다음과 같이 입력되는 body값을 수정하였습니다.
수정 방향을 정리하고 나서 코드를 다시 만졌습니다. 코드의 주요 흐름은 구단주 체크하고, 매치 검증 후 경기 결과 데이터를 준비합니다. 그 후 트랜잭션을 시작하여 그 안에서 경기 결과 처리, 경기 결과 집계를 수행한 후 트랜잭션 커밋 및 롤백을 수행합니다. 최종적으로 트랜잭션 종료하면서 코드는 기능을 마무리 합니다.
분명 아래 코드는 수정이 필요한 코드라 생각합니다. 메서드 안에서 동작하는 기능 중 분명 재사용 할 수 있는 기능들이 있음에도 한 메서드 안에서만 동작하게 한건 수정해야할 점이라 여겨집니다.
async resultMathfinal(
userId: number,
matchId: number,
resultMembersDto: ResultMembersDto,
) {
// 구단주 체크
const homeCreator = await this.verifyTeamCreator(userId);
const match = await this.verifyOneMatch(matchId, homeCreator[0].id);
// goals 정보를 담을 배열 초기화
let goalsArray = [];
let assistsArray = [];
let yellowCardsArray = [];
let redCardsArray = [];
let savesArray = [];
let goalsCount =0;
const matches = await this.matchResultRepository.find({where:{id:matchId}});
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try{
for (const resultMemberDto of resultMembersDto.results) {
// 각 DTO에서 필요한 데이터 추출
const memberId = resultMemberDto.memberId;
const goals = resultMemberDto.goals;
const assists = resultMemberDto.assists;
const yellowCards = resultMemberDto.yellowCards;
const redCards = resultMemberDto.redCards;
const saves = resultMemberDto.save;
// 해당 팀의 멤버인지 체크
await this.isTeamMember(homeCreator[0].id, memberId);
// DTO를 사용하여 데이터베이스에 저장
const playerStats = this.playerStatsRepository.create({
team_id: homeCreator[0].id,
match_id: match.id,
member_id: memberId,
assists,
goals,
yellow_cards: yellowCards,
red_cards: redCards,
save: saves,
});
if (!playerStats) {
throw new NotFoundException('경기결과 기록을 생성할 수 없습니다.');
}
if (goals > 0) {
goalsArray.push({ memberId, count: goals });
goalsCount+=goals;
}
if (assists > 0) {
assistsArray.push({ memberId, count: assists });
}
if (yellowCards > 0) {
yellowCardsArray.push({ memberId, count: yellowCards });
}
if (redCards > 0) {
redCardsArray.push({ memberId, count: redCards });
}
if (saves > 0) {
savesArray.push({ memberId, count: saves });
}
// 데이터베이스에 저장
await queryRunner.manager.save(playerStats);
}
const matchResultCount = await this.matchResultCount(matchId);
console.log('matchResult count:',matchResultCount.count);
let this_clean_sheet = false;
let other_clean_sheet = false;
// 한 팀이 등록한 상태라면 팀 스탯 생성
if (matchResultCount.count === 2) {
// 모든 경기 결과에서 goals이 null이 아닌지 확인
const allGoalsNotNull = matches.every(match => match.goals !== null);
if (allGoalsNotNull) {
// 모든 goals의 값이 null이 아닌 경우의 처리를 여기에 작성합니다.
throw new NotFoundException('이미 경기결과가 집계 되었습니다.');
}
const otherTeam = await this.matchResultRepository.findOne({
where:{match_id:matchId, team_id: Not(homeCreator[0].id)}
})
let this_score = goalsCount;
let other_score = otherTeam.goals.reduce((total, goal) => total + goal.count, 0);
let this_win = 0;
let this_lose = 0;
let this_draw = 0;
let other_win = 0;
let other_lose = 0;
let other_draw = 0;
if (this_score > other_score) {
this_win += 1;
other_lose += 1;
} else if (this_score < other_score) {
other_win += 1;
this_lose += 1;
} else {
this_draw += 1;
other_draw += 1;
}
if(other_score===0) this_clean_sheet = true;
if(this_score===0) other_clean_sheet = true;
// 홈팀 스탯 생성
const getThisTeamStats = await this.teamTotalGames(homeCreator[0].id);
const thisTeamStats = await this.teamStatsRepository.findOne({
where:{team_id:homeCreator[0].id }
});
let thisTeamStatsWins= 0;
let thisTeamStatsLoses= 0;
let thisTeamStatsDraws= 0;
// thisTeamStats가 존재하면 기존 wins 값에 this_win을 더함
if (thisTeamStats) {
thisTeamStatsWins = thisTeamStats.wins + this_win;
thisTeamStatsLoses = thisTeamStats.loses + this_lose;
thisTeamStatsDraws = thisTeamStats.draws + this_draw;
await this.teamStatsRepository.update({
team_id: homeCreator[0].id},
{
wins: thisTeamStatsWins,
loses: thisTeamStatsLoses,
draws: thisTeamStatsDraws,
total_games: getThisTeamStats?getThisTeamStats.total_games+1:1,
});
} else {
// thisTeamStats가 존재하지 않으면 this_win만 사용
thisTeamStatsWins = this_win;
thisTeamStatsLoses = this_lose;
thisTeamStatsDraws = this_draw;
const thisTeamResult = await this.teamStatsRepository.create({
team_id: homeCreator[0].id,
wins: thisTeamStatsWins,
loses: thisTeamStatsLoses,
draws: thisTeamStatsDraws,
total_games: 1,
});
await queryRunner.manager.save('team_statistics', thisTeamResult);
}
// 상대팀 스탯 생성
const getOtherTeamStats = await this.teamTotalGames(otherTeam.team_id);
const otherTeamStats = await this.teamStatsRepository.findOne({
where:{team_id:otherTeam.team_id }
});
let otherTeamStatsWins= 0;
let otherTeamStatsLoses= 0;
let otherTeamStatsDraws= 0;
// thisTeamStats가 존재하면 기존 wins 값에 this_win을 더함
if (otherTeamStats) {
otherTeamStatsWins = otherTeamStats.wins + other_win;
otherTeamStatsLoses = otherTeamStats.loses + other_lose;
otherTeamStatsDraws = otherTeamStats.draws + other_draw;
await this.teamStatsRepository.update({
team_id: otherTeam.team_id},
{
wins: otherTeamStatsWins,
loses: otherTeamStatsLoses,
draws: otherTeamStatsDraws,
total_games: getOtherTeamStats?getOtherTeamStats.total_games+1:1,
});
} else {
// thisTeamStats가 존재하지 않으면 this_win만 사용
otherTeamStatsWins = other_win;
otherTeamStatsLoses = other_lose;
otherTeamStatsDraws = other_draw;
const otherTeamResult = await this.teamStatsRepository.create({
team_id: otherTeam.team_id,
wins: otherTeamStatsWins,
loses: otherTeamStatsLoses,
draws: otherTeamStatsDraws,
total_games: 1,
});
await queryRunner.manager.save('team_statistics', otherTeamResult);
}
await queryRunner.manager.update(
'match_results',
{match_id:matchId,team_id:otherTeam.team_id},
{
clean_sheet:other_clean_sheet
});
}
await queryRunner.manager.update(
'match_results',
{match_id:matchId,team_id:homeCreator[0].id},
{
goals:goalsArray,
assists:assistsArray,
yellow_cards:yellowCardsArray,
red_cards:redCardsArray,
saves:savesArray,
clean_sheet:this_clean_sheet
});
await queryRunner.commitTransaction();
}catch(error){
await queryRunner.rollbackTransaction();
if (error instanceof HttpException) {
// HttpException을 상속한 경우(statusCode 속성이 있는 경우)
throw error;
} else {
// 그 외의 예외
throw new InternalServerErrorException('서버 에러가 발생했습니다.');
}
}finally{
await queryRunner.release();
}
}
이에 아래와 같이 위 코드를 리팩토링 해보았습니다.
코드 리팩토링
기존 작성한 위의 메서드 안에서 선수 통계 처리, 경기 결과 집계, 팀 통계 업데이트 의 세 기능을 분리해보았습니다.
//선수 통계 처리
async processPlayerStats(resultMemberDto, homeCreatorId, matchId, queryRunner) {
const memberId = resultMemberDto.memberId;
const goals = resultMemberDto.goals;
const assists = resultMemberDto.assists;
const yellowCards = resultMemberDto.yellowCards;
const redCards = resultMemberDto.redCards;
const saves = resultMemberDto.save;
// 해당 팀의 멤버인지 체크
await this.isTeamMember(homeCreatorId, memberId);
// DTO를 사용하여 데이터베이스에 저장
const playerStats = this.playerStatsRepository.create({
team_id: homeCreatorId,
match_id: matchId,
member_id: memberId,
assists,
goals,
yellow_cards: yellowCards,
red_cards: redCards,
save: saves,
});
if (!playerStats) {
throw new NotFoundException('경기결과 기록을 생성할 수 없습니다.');
}
// 데이터베이스에 저장
await queryRunner.manager.save(playerStats);
}
// 경기 결과 집계
async aggregateMatchResult(matchId, homeCreatorId, goalsCount, queryRunner) {
const matches = await this.matchResultRepository.find({ where: { id: matchId } });
// 모든 경기 결과에서 goals이 null이 아닌지 확인
const allGoalsNotNull = matches.every(match => match.goals !== null);
if (allGoalsNotNull) {
throw new NotFoundException('이미 경기결과가 집계 되었습니다.');
}
const otherTeam = await this.matchResultRepository.findOne({
where: { match_id: matchId, team_id: Not(homeCreatorId) }
});
let this_score = goalsCount;
let other_score = otherTeam.goals.reduce((total, goal) => total + goal.count, 0);
let this_win = 0;
let this_lose = 0;
let this_draw = 0;
let other_win = 0;
let other_lose = 0;
let other_draw = 0;
if (this_score > other_score) {
this_win += 1;
other_lose += 1;
} else if (this_score < other_score) {
other_win += 1;
this_lose += 1;
} else {
this_draw += 1;
other_draw += 1;
}
let this_clean_sheet = other_score === 0;
let other_clean_sheet = this_score === 0;
return { this_win, this_lose, this_draw, other_win, other_lose, other_draw, this_clean_sheet, other_clean_sheet };
}
// 팀 통계 업데이트
async updateTeamStatistics(teamId, win, lose, draw, totalGames, queryRunner) {
const teamStats = await this.teamStatsRepository.findOne({ where: { team_id: teamId } });
if (teamStats) {
// 기존 통계 업데이트
await this.teamStatsRepository.update(
{ team_id: teamId },
{
wins: teamStats.wins + win,
loses: teamStats.loses + lose,
draws: teamStats.draws + draw,
total_games: teamStats.total_games + totalGames,
}
);
} else {
// 새로운 통계 생성
const newTeamStats = this.teamStatsRepository.create({
team_id: teamId,
wins: win,
loses: lose,
draws: draw,
total_games: totalGames,
});
await queryRunner.manager.save('team_statistics', newTeamStats);
}
}
기능별로 나눈 위 코드를 다시 적용하면 아래와 같이 할 수 있습니다.
async resultMathfinal(userId, matchId, resultMembersDto) {
// ...
await queryRunner.startTransaction();
try {
for (const resultMemberDto of resultMembersDto.results) {
await this.processPlayerStats(resultMemberDto, homeCreator[0].id, match.id, queryRunner);
}
const matchResult = await this.aggregateMatchResult(matchId, homeCreator[0].id, goalsCount, queryRunner);
await this.updateTeamStatistics(homeCreator[0].id, matchResult.thisWin, matchResult.thisLose, matchResult.thisDraw, queryRunner);
await this.updateTeamStatistics(otherTeam.team_id, matchResult.otherWin, matchResult.otherLose, matchResult.otherDraw, queryRunner);
await queryRunner.commitTransaction();
} catch(error) {
// 에러 처리
await queryRunner.rollbackTransaction();
// ...
} finally {
await queryRunner.release();
}
}
코드를 난잡하게 작성하고 다시 기능별로 정리하면서 코드 리팩토링이 주는 이점에 대해 체감할 수 있었습니다. 코드가 한결 이해하기 편해졌고, 무엇보다 코드 재사용성이 증가하였습니다. 위의 기능은 통계 이외에서는 사용하기 어렵겠지만 기능별 분리를 시도해봤다는 점에서 배워가는게 많았던 시간입니다.
▼ 이전 진행한 프로젝트들 ▼
'내일배움캠프 > 축구팀 관리 프로젝트' 카테고리의 다른 글
축구팀 관리프로젝트 17일차 - 전술 화면 조회 및 저장 개발 중 (1) | 2024.01.29 |
---|---|
축구팀 관리 프로젝트 16일차 - 대용량 트래픽을 처리하는 두가지 방법 (1) | 2024.01.28 |
축구팀 관리 프로젝트 14일차 - 전술 설정 화면, nestjs cron 사용 (1) | 2024.01.26 |
축구팀 관리 프로젝트 13일차 - 포메이션 관리 화면, ts(2339) 오류 (1) | 2024.01.25 |
최종프로젝트 12일차 경기 일정 조회 작업, 쿼리 최적화에 대해 (0) | 2024.01.23 |