카테고리 없음

set별 점수 관리 API 구현과 리팩토링: 내 사고 과정과 결과

Tree 2025. 4. 12. 11:46

들어가며

이번 글에서는 토너먼트 관리 시스템의 점수 처리 API를 개선한 과정에서 제가 어떻게 생각하고 결정을 내렸는지, 그리고 어떤 결과물을 만들었는지 솔직하게 공유합니다.

초기 상황: 분리된 기능과 반복되는 API 호출

처음 시스템을 만들 때는 "단순한 기능"에 초점을 맞춰 API를 설계했습니다.

PUT /~/:matchId/score   (점수 증가)
DELETE /~/:matchId/score   (점수 감소)

이 설계의 기저에는 단순한 생각이 있었습니다:

  • "점수를 올린다" → PUT 메서드
  • "점수를 내린다" → DELETE 메서드

코드는 다음과 같았습니다:

export class UpdateScoreDto {
  @IsEnum(Team)
  team: Team;// A팀인지 B팀인지
}

@Put(':matchId/score')
async incrementScore(
  @Param('matchId') matchId: string,
  @Body() dto: UpdateScoreDto,
) {
  return this.matchService.incrementScore(matchId, dto.team);
}

@Delete(':matchId/score')
async decrementScore(
  @Param('matchId') matchId: string,
  @Body() dto: UpdateScoreDto,
) {
  return this.matchService.decrementScore(matchId, dto.team);
}

async incrementScore(matchId: string, team: Team) {
  const match = await this.findOneRaw(matchId);
  if (team === Team.A) {
    match.teamAScore += 1;
  } else {
    match.teamBScore += 1;
  }
  return this.matchRepository.save(match);
}

처음에는 이 설계가 괜찮다고 생각했습니다. 다만, 실제로 제가 설계했던 API를 로컬에서 프론트엔드와 상호작용하고 직접 사용해보며 이 설계가 잘못 되었다는 것을 깨달았습니다.

1. 만약 실수로 점수를 잘못 입력했다면?

감소 API를 호출한 후 다시 증가 API를 호출해야 했습니다.

이 때문에 불필요한 API 호출이 필요한 상황이 자주 발생했습니다.

2. 가장 크리티컬한 문제는 SET별 점수를 알수 없었다는 것입니다.

1. SET
2. SET
3. SET

저는 초반에는 단순히 ‘점수 올리기’ 에만 집중을 했기 때문에, 이러한 상황까지 고려하지 못하고 API를 설계했습니다.

고민과 인사이트

이런 상황을 개선하기 위한 고민 과정에서 몇 가지 중요한 깨달음을 얻었습니다:

  1. 명령형 API vs 선언형 API: 저는 "점수를 1 올려라"라는 '명령형' 방식으로 생각하고 있었지만, 사용자 관점에서는 "점수를 1:0로 설정하라"라는 '선언형' 방식이 훨씬 직관적이었습니다.
  2. 작업 단위의 재정의: "점수 변경"이라는 작은 작업 단위가 아니라, "세트 결과 관리"라는 더 큰 작업 단위로 생각하기 시작했습니다.
  3. 관계형 데이터 모델: 점수가 매치에 직접 속하는 것이 아니라, 매치는 여러 세트를 가지고 각 세트가 점수를 갖는 구조가 더 자연스럽다는 점을 깨달았습니다. (이건 저에게 가장 중요한 포인트였습니다.)

설계 결정: 논리적 관계와 통합 API

이러한 고민을 바탕으로 다음과 같은 설계 결정을 내렸습니다:

1. 세트 엔티티 도입

여기서 더해 디벨롭한 내용들이 이있습니다.

  • 명시적인 승자 저장: 조회 시 매번 계산하지 않도록 승자 정보 직접 저장하는 방법을 택했습니다.

2. 통합 API 설계

모든 세트 관련 작업을 하나의 엔드포인트로 통합했습니다.

  • 현재 내가 진행하고 있는 세트가 몇세트인지
  • teamA의 스코어는 몇인지
  • teamB의 스코어는 몇인지
  • 위너는 누구인지

등을 포함했습니다.

그 후에 디벨롭된 API를 통해 다음 작업을 모두 수행할 수 있습니다

  • 세트 생성/추가
  • 점수 업데이트
  • 승자 지정
  • 세트 초기화(취소)

구현 결과: 강력하고 유연한 통합 메서드

강력하고 유연한 통합 메서드라고 생각합니다. 핵심 아이디어를 구현한 manageMatchSets 메서드의 일부입니다.이 메서드 하나가 수많은 시나리오를 처리합니다:

  • 개별 세트 점수 설정
  • 승자 지정 (자동 또는 수동)
  • 세트 초기화

Before & After

// 점수를 2:1로 설정하는 과정// 1. 모든 점수 초기화
await api.decrementScore(matchId, 'A');// 여러번 호출 필요할 수도
await api.decrementScore(matchId, 'B');// 여러번 호출 필요할 수도// 2. 새 점수 설정
await api.incrementScore(matchId, 'A');
await api.incrementScore(matchId, 'A');
await api.incrementScore(matchId, 'B');

// 3. 승자 설정 (별도 API)
await api.setWinner(matchId, 'A');

이후: 단일 API 호출로 해결

await api.manageMatchSets(matchId, {
  세트넘버: 1,// 첫 번째 세트
  팀A의점수: 2,// A팀 2점
  팀B의점수: 1,// B팀 1점
  위너팀명: 'A'// A팀 승리
  ..//등등
});

사용자 관점에서 생각하기

이 리팩토링 과정에서 가장 큰 교훈은 "개발자 관점"에서 "사용자 관점"으로 사고를 전환하는 것의 중요성이었습니다.

  1. 명령형에서 선언형으로: "이 작업을 어떻게 수행하는가"가 아닌 "최종적으로 무엇을 원하는가"에 초점을 맞추는 API
    1. 저는 마음이 급해 당장 눈앞에 주어진 개발에 집중하는 편이었는데, 그게 더 멀리 돌아가는 길이라는 것을 이번 기능 구현을 하며 깨달았습니다.
    2. 저는 그렇기 때문에 기획 문서와 피그마 와이어프레임을 더 꼼꼼히 봐야한다고 느꼈습니다. (마음만 급한 저에게, 이게 너무 어려웠던 것 같습니다.)
  2. 적절한 작업 단위 찾기: 너무 작은 작업 단위는 여러 번의 API 호출을 요구하고, 너무 큰 작업 단위는 유연성을 제한합니다.

마무리, 프론트엔드 개발자로서

모든 것이 처음이었기 때문에 많은 길을 돌아왔지만 결국 동작하는 API를 만들었고 프론트엔드 개발자로서 느낀 점을 몇자. ㅓㄱ어보려 합니다.

저는 이번 기회를 통해 API를 사용하는 사람의 관점으로 바라볼 수 있게 되었습니다. 저는 프론트엔드 개발자로서 매번 일상적으로 API를 소비하는 입장이었습니다. 그런데 이번에는 그 API를 직접 설계하는 역할을 맡게 되었고, 이로 인해 몇 가지 중요한 깨달음을 얻었습니다. 

기술적 역량 확장의 기회

프론트엔드 개발자지만 백엔드 로직을 이해하고 다루면서, 제 기술적 시야가 넓어졌습니다. 처음에는 단순히 "이 API를 어떻게 호출할까?"라는 고민만 했지만, 점차 "이 API는 왜 이렇게 설계되었을까?"라는 질문을 스스로 하게 되었습니다. 결과적으로, API 자체를 개선함으로써 프론트엔드 코드도 훨씬 간결하고 견고해졌습니다.

더 나아가 잘 설계된 백엔드 API는 프론트엔드 개발자를 편하게 한다는 것 까지 알게되었습니다. 이전에는 주로 "이 API에서 이런 데이터를 더 주실 수 있나요?"라는 요청을 했다면, 차츰 "이런 비즈니스 로직을 고려했을 때, 이런 리소스 모델링이 더 효율적이지 않을까요?"라는 식의 대화를 할수 있을 날까지 달려보고 싶다는 생각이 들었네요.

경계를 넘는 개발자로

결국 이번 API 구현과 리팩토링 경험은 제게 "프론트엔드 개발자"라는 레이블을 넘어서 "문제를 해결하는 개발자"로 성장할 수 있는 기회였습니다. 기술적 경계를 넘나들며 시스템 전체를 바라보는 시각은 앞으로의 커리어에 큰 자산이 될 것이라 확신합니다. 사실은 저에게 이런 것들이 모두 챌린징한 일들입니다만. 하지만 앞으로도 "사용자 관점"과 "시스템 관점"을 모두 고려한 API 설계에 참여해보고싶습니다.

설령 그것이 틀린 설계일지더라도, 많은 것들을 구현해나가면서 그것이 “왜” 틀리고”왜” 맞는지를 몸으로 직접 체감해보고 싶습니다. 그리고 이런 경험들이 쌓여 언젠가는 기술적 의사결정에 더 큰 영향력을 발휘할 수 있는 개발자가 될거라 믿습니다.