BindProject

[Backend] Image모듈 개발기 2편: 이미지도 생명주기가 있다면 – 도메인 모델과 상태 설계

dding-shark 2025. 6. 21. 15:13
728x90

2편. 이미지 상태 기반 도메인 설계: TEMP, CONFIRMED, 그리고 그 이후


도입

이미지 업로드는 단순한 파일 저장이 아니다.
사용자 행동에 따라 임시 업로드, 확정, 삭제 대기, 완전 삭제상태 흐름을 갖는다.

Bind 프로젝트의 이미지 모듈은 이 흐름을 명확하게 표현할 수 있는 상태 기반 도메인 모델링으로 설계했다.
단순한 URL 저장이 아니라, 이미지 객체의 상태 전이에 기반한 명세 중심 설계를 소개한다.


설계 목표

  • 명확한 상태 기반 모델링을 통해 추적 가능한 이미지 흐름을 보장한다.
  • 단일 API가 아닌, 후속 행동(BFF 확정 요청 등)에 따라 확장 가능한 구조를 만든다.
  • 서비스 레이어에 로직이 집중되지 않도록 검증 로직과 상태 전이 책임을 분리한다.

설계 고려사항

상태 전이 흐름은 이렇게 작동한다

행동 상태 흐름
유저가 프로필 사진 업로드 TEMP
회원가입 완료되면 → BFF가 확정 요청 TEMP → CONFIRMED
게시글 수정으로 이미지 제거 CONFIRMED → PENDING_DELETE
1시간 이상 삭제 대기된 파일 PENDING_DELETE → 삭제 스케줄러가 제거

boolean isDeleted 같은 단순한 플래그가 아닌, 도메인 중심 상태 흐름이 요구되었다.


구현 설명

Image 엔티티

@Entity
public class Image {
    private Long id;
    private String uuidName;
    private String storedPath;
    private ImageStatus status;
    private ResourceCategory category;
    private String referenceId;
    private String uploaderId;
    ...
}

→ 저장 위치, 용량, MIME 타입, URL 등 기본 메타정보 외에도 status, referenceId, uploaderId는 상태 흐름을 위한 핵심 필드다.


ImageStatus Enum

public enum ImageStatus {
    TEMP,
    CONFIRMED,
    PENDING_DELETE,
    REJECTED;
}
  • TEMP: 업로드 직후, 사용 여부 확정 전 상태
  • CONFIRMED: 게시글, 프로필 등 실제 데이터와 연결됨
  • PENDING_DELETE: 삭제 요청은 되었지만 아직 삭제되지 않은 상태
  • REJECTED: 필터링 실패(NSFW 등) 시 사용 가능

→ 추후 상태는 ARCHIVED, EXPIRED로 확장할 수 있도록 열어두었다.


상태 검증: ImageValidator

@Component
public class ImageValidator {
    public void validateUser(Image image, String userId) {
        if (!image.getUploaderId().equals(userId)) {
            throw new ImageException(UNAUTHORIZED_ACCESS);
        }
    }
    ...
}
  • 이미지가 현재 유저의 것인지
  • TEMP 상태인지
  • 요청한 category와 일치하는지

→ 비즈니스 흐름이 많아질수록 검증 조건이 늘어날 수 있는데, Validator로 분리해 응집도 있는 단위로 묶었다.


상태 변경 책임 분리: ImageStatusChanger

@Component
public class ImageStatusChanger {
    public void changeStatus(Image image, ImageStatus expected, ImageStatus to, ImageErrorCode error) {
        if (expected != null && image.getStatus() != expected) {
            throw new ImageException(error);
        }
        image.setStatus(to);
    }
}
  • 상태 변경은 모두 changeStatus()를 거치도록 해 무분별한 상태 조작을 막는다.
  • expected 상태가 있으면 사전 검증, 없다면 무조건 전이.

→ 추후 이벤트 발행(ConfirmedEvent 등) 시점도 이 컴포넌트에서 분리 가능하다.


회고 및 확장 가능성

  • ImageStatus를 도입함으로써 서비스 흐름이 눈에 보이도록 바뀌었다.
  • 예전처럼 isDeleted == true 같은 의미 모호한 필드가 아니라, 도메인 중심의 상태 전이가 가능해졌다.
  • Validator, StatusChanger를 분리하면서 테스트 커버리지 확보가 쉬워졌고, Mocking 단위도 명확해졌다.
  • 확정 시점에 Kafka 이벤트 발행이 필요해질 경우, changeStatus 내부에서 트리거하거나 ConfirmedEvent 도메인 이벤트로 추상화할 수 있다.

다음 글 예고

3편. 이미지 서비스 흐름과 테스트 전략

  • upload → confirm → delete 흐름을 하나의 유즈케이스로 설명하고
  • ImageService가 어떻게 작은 책임으로 나뉘는지
  • 단위 테스트는 어떤 케이스를 중심으로 작성하는지
  • 스케줄러 삭제 흐름과 실패 대비 전략

을 깊게 다룬다. 실질적인 코드 흐름과 테스트 전략을 정리할 예정이다.


728x90