본문 바로가기
  • Let's study
회고/GameLog 프로젝트

2차 프로젝트: GameLog 2일차

by 코딩고수이고파 2026. 9. 15.

UserGame 설계 과정 — 데이터 생명주기와 책임 범위 고민

게임 라이브러리와 리뷰 기능을 구현하면서 UserGame을 어떻게 관리할지 고민했다.

단순히 게임을 라이브러리에 등록하고 상태를 변경하는 정도로 생각했지만, 리뷰 작성 기능까지 연결하면서 UserGame의 생성·수정·삭제 시점과 각 기능의 책임 범위를 명확하게 정할 필요가 있었다.

1. 리뷰 작성과 게임 등록의 책임이 겹치는 문제

팀원별로 기능을 나누어 구현하고 있었는데, 리뷰 작성 기능에서도 플레이 상태나 플레이 기록을 수정해야 했다.

예를 들어 리뷰를 작성하면서 플랫폼이나 플레이 시간, 플레이 상태를 함께 입력할 수 있다.

그렇다면 리뷰를 담당하는 팀원이 UserGameRepository나 UserGame을 직접 수정해야 하는데, 이렇게 되면 게임 등록을 담당하는 나와 리뷰 담당자의 구현 범위가 겹치는 문제가 있었다.

생각한 해결 방법

처음에는 리뷰 담당자가 필요한 UserGame 로직을 직접 구현하는 방법도 생각했다.

하지만 이렇게 하면 같은 데이터를 수정하는 로직이 여러 곳에 생길 수 있고, 이후 수정이 필요할 때 어느 곳을 변경해야 하는지도 모호해질 수 있었다.

그래서 UserGame의 변경 책임을 하나의 서비스에 모으고, 다른 기능에서는 해당 서비스를 호출하는 방식을 생각했다.

선택한 방법과 이유

UserGame을 변경하는 책임은 UserGameService가 갖도록 했다.

C
└── UserGameService
    ├── addGameToLibrary()
    ├── removeGameFromLibrary()
    ├── changePlayStatus()
    └── updatePlayRecord()

D
└── ReviewService
    └── createReview()
        ├── UserGameService 호출
        └── Review 저장

리뷰 작성이라는 전체 작업은 ReviewService에서 처리하되, 실제 UserGame을 변경할 때는 UserGameService를 호출하도록 했다.

이를 통해 기능의 담당 범위와 데이터 변경 책임을 분리할 수 있다고 판단했다.

2. 기록이 없을 때 UserGame 데이터를 삭제해야 하는지

다음으로 고민한 것은 UserGame의 생명주기였다.

GameLog에서는 게임의 플레이 상태와 리뷰를 별도로 관리한다.

따라서 사용자가 상태도 해제하고 리뷰도 삭제한다면 해당 게임에 대한 기록이 아무것도 남지 않게 된다.

처음부터 상태와 리뷰가 모두 없을 경우에는 라이브러리에서 UserGame을 제거한다는 기준을 생각하고 있었다.

문제는 이때 실제 DB에서 DELETE를 하는 것이 좋은지였다.

예를 들어 사용자가 게임을 등록했다가 상태를 해제하고, 나중에 다시 등록한다면 다음과 같은 상황이 발생할 수 있다.

INSERT
 ↓
DELETE
 ↓
INSERT

사용자가 게임을 반복해서 등록하고 해제할수록 INSERT와 DELETE가 반복된다.

UserGame은 단순한 라이브러리 목록이 아니라 사용자와 게임의 관계를 나타내고 리뷰와 플레이 기록도 연결되는 데이터이기 때문에, **이 데이터를 매번 삭제하는 것이 적절한가?**라는 고민이 생겼다.

생각한 해결 방법

방법 1. 실제로 DELETE한다.

상태와 리뷰가 모두 없어지면 UserGame을 삭제하고, 다시 등록하면 새로운 UserGame을 생성한다.

INSERT → DELETE → INSERT

 

구조가 단순하다는 장점이 있다.

하지만 같은 게임을 다시 등록할 때 기존 데이터를 삭제하고 새로 생성하게 된다는 점이 아쉬웠다.

방법 2. UserGame은 유지하고 라이브러리 활성 여부만 관리한다.

isInLibrary와 같은 값을 두고 실제 데이터의 존재 여부와 현재 라이브러리에 노출되는지 여부를 분리하는 방법이다.

isInLibrary = true
→ 라이브러리에 표시

isInLibrary = false
→ 라이브러리에서 숨김

 

이 경우 다시 등록할 때 기존 데이터를 UPDATE하여 활성화할 수 있다.

INSERT → UPDATE → UPDATE

선택한 방법과 이유

최종적으로 isInLibrary를 사용해 UserGame을 비활성화하는 방법을 선택했다.

UserGame을 실제로 삭제하는 대신 라이브러리에 포함되는 상태만 변경하면, 기존 데이터를 유지하면서 다시 활성화할 수 있기 때문이다.

특히 UserGame은 리뷰와 플레이 기록을 연결하는 기준이기 때문에, 라이브러리에서 보이지 않는 것과 데이터 자체가 존재하지 않는 것을 구분하는 것이 더 적절하다고 판단했다.

따라서 다음과 같은 기준으로 관리하기로 했다.

상태 또는 리뷰 있음
→ UserGame 유지
→ isInLibrary = true

상태와 리뷰 모두 없음
→ UserGame 유지
→ isInLibrary = false

3. isInLibrary를 사용하면서 생긴 새로운 고민

그런데 isInLibrary를 도입하자 새로운 문제가 생겼다.

처음 게임에 대한 기록을 생성하는 경우에는 UserGame을 INSERT해야 한다.

반면 이미 UserGame이 존재하고 isInLibrary = false인 상태에서 다시 상태를 ON하면 새로운 데이터를 INSERT하는 것이 아니라 기존 데이터를 UPDATE해야 한다.

즉, 프론트엔드에서 단순히

POST → INSERT
PATCH → UPDATE

 

로 나누기 어려워졌다.

생각한 방법

프론트엔드가 현재 UserGame의 존재 여부를 확인한 뒤 POST와 PATCH를 구분해서 호출하는 방법도 생각할 수 있었다.

하지만 이렇게 하면 UserGame이 존재하는지, 활성화되어 있는지 판단하는 로직이 프론트엔드에도 들어가게 된다.

클라이언트가 DB의 상태를 고려해 요청 방식을 결정하도록 만드는 것보다, 서버가 현재 데이터를 확인하고 적절한 작업을 결정하는 것이 더 적절하다고 판단했다.

선택한 방법과 이유

그래서 프론트엔드에서는 게임 기록을 저장한다는 의미의 POST 요청 하나만 보내고, 서버에서 기존 UserGame의 존재 여부를 확인해 INSERT 또는 UPDATE를 판단하도록 했다.

POST 요청
   ↓
기존 UserGame 존재?
   ├── NO → INSERT
   │
   └── YES → UPDATE

이렇게 하면 프론트엔드는 해당 요청이 실제로 INSERT가 되는지 UPDATE가 되는지 알 필요가 없다.

즉, 데이터 상태를 판단하는 책임을 서버에 두고 프론트엔드의 로직을 단순하게 유지할 수 있었다.

4. UserGame을 전체 수정할 것인가, 필요한 값만 수정할 것인가?

UserGame의 업데이트 방식을 구현하면서 또 하나 고민한 부분이 있었다.

Wishlist, Playing처럼 클릭 한 번으로 상태를 변경할 수 있는 값이 있는 반면, 플랫폼이나 플레이 시간, 시작일처럼 한 번에 여러 데이터를 입력하거나 수정하는 경우도 있다.

그렇다면 UserGame을 수정할 때마다 변경된 값만 UPDATE할 것인지, 아니면 UserGame에 들어가는 데이터를 전체적으로 받아 한 번에 UPDATE할 것인지 고민했다.

예를 들어 단순히 Wishlist 상태 하나만 변경했는데도 UserGame의 모든 데이터를 수정하는 방식은 불필요한 데이터까지 함께 처리하게 된다.

반대로 변경되는 값마다 별도의 UPDATE 로직을 만들면 API와 서비스 로직이 지나치게 세분화될 수 있다.

선택한 방법과 이유

이번 프로젝트에서는 사용자의 행동 단위에 맞춰 상태 변경 메서드를 두는 방식을 선택했다.

특히 Wishlist, Playing과 같이 사용자가 클릭 한 번으로 빠르게 ON/OFF할 수 있는 상태는 각각의 변경 의도를 나타내는 메서드로 관리했다.

userGame.activateWishlist();
userGame.deactivateWishlist();

userGame.startPlaying();
userGame.stopPlaying();

 

이렇게 하면 단순히 전체 UserGame 데이터를 다시 전달하지 않고도 사용자가 어떤 행동을 했는지 명확하게 표현하면서 필요한 상태만 변경할 수 있다.

반면 플랫폼, 플레이 시간, 날짜처럼 여러 값이 함께 수정되는 플레이 기록은 별도의 updatePlayRecord()를 통해 한 번에 변경하도록 구분했다.

결과적으로 모든 UPDATE를 하나의 방식으로 처리하기보다, 변경되는 데이터와 사용자의 행동 특성에 따라 업데이트 방식을 나누는 방향을 선택했다.

결과적으로 배운 점

이번 고민을 통해 단순히 CRUD를 구현하는 것보다 데이터의 생명주기와 변경 책임을 먼저 설계하는 것이 중요하다는 것을 배웠다.

특히 이번 UserGame 설계에서는 데이터의 존재와 라이브러리 활성 상태를 분리하고, UserGame의 변경 책임을 한 곳에서 관리하도록 했다. 또한 변경되는 데이터의 특성에 따라 상태값은 개별 메서드로, 여러 플레이 기록은 한 번에 수정하도록 구분했다.

'회고 > GameLog 프로젝트' 카테고리의 다른 글

2차 프로젝트: GameLog 3일차 - 403 오류  (0) 2026.09.16
2차 프로젝트: GameLog 1일차  (0) 2026.09.14

댓글