본문 바로가기
내일배움캠프/축구팀 관리 프로젝트

축구팀 관리 프로젝트 15일차 - 경기 후 기록 등록하는 로직 수정

by 코드스니펫 2024. 1. 27.
반응형

축구팀 관리 프로젝트 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();
    }
}

 

 

코드를 난잡하게 작성하고 다시 기능별로 정리하면서 코드 리팩토링이 주는 이점에 대해 체감할 수 있었습니다. 코드가 한결 이해하기 편해졌고, 무엇보다 코드 재사용성이 증가하였습니다. 위의 기능은 통계 이외에서는 사용하기 어렵겠지만 기능별 분리를 시도해봤다는 점에서 배워가는게 많았던 시간입니다.

 

▼ 이전 진행한 프로젝트들 ▼

 

 

내일배움캠프 Node트랙 심화 프로젝트 역할 및 진행사항

내일배움캠프 Node트랙 심화 프로젝트 역할 및 진행사항 이번 프로젝트는 팀 프로젝트로 Node트랙 심화 프로젝트를 진행하게 되었습니다. 프로젝트를 시작하며 팀에서 맡은 역할과 현재 진행사항

lemonlog.tistory.com

 

 

내일배움캠프 백오피스 프로젝트 - 펫시터 매칭 사이트 후기, 소감

내일배움캠프 백오피스 프로젝트 - 펫시터 매칭 사이트 후기, 소감 일주일간 팀원과 작업한 펫시터 매칭 사이트가 끝났습니다. 여러 우여곡절이 있었지만 목표한 대로 마쳤기에 만족하고 있습

lemonlog.tistory.com