Auth 리팩토링

ChoRong0824·2025년 5월 3일

프로젝트

목록 보기
2/2

프로젝트 실행하면 (readme에 실행 방법 기재해둠)
-> 참고로 간혹 빌드가 잘 안될 때 있는데, 그럴땐 걍 down하고, 재 빌드 하면 됨.

docker-compose -f docker-compose.local.yml down
docker-compose -f docker-compose.local.yml build
docker-compose -f docker-compose.local.yml up -d

이런 식으로 에러 뜨는거는 그냥 포트 번호 수정해주면 됨.
이미 3306 포트를 다른 프로세스(예: 로컬 MySQL 서버)가 점유 중이라서 Docker가 사용할 수 없다는 뜻

이런 식으로 뜨는데, 이건

failed to solve: eclipse-temurin:17-jdk-alpine: no match for platform in manifest...

현재 사용하는 Docker Desktop (예: Apple Silicon 기반 맥) 환경과 이미지가 호환되지 않아서 발생한 문제.

즉, eclipse-temurin:17-jdk-alpine 이미지가 ARM64 (예: M1/M2 Mac) 플랫폼을 지원하지 않거나, 해당 플랫폼에 대한 manifest가 등록되어 있지 않아서 생기는 문제입니다.

해결 방법
방법 1: 이미지 변경

# Dockerfile에서
# FROM eclipse-temurin:17-jdk-alpine
FROM eclipse-temurin:17-jdk   # <- alpine 제거
# alpine 버전은 용량은 작지만, ARM 호환성 문제가 자주 발생함.
# 일반 jdk 버전은 호환성이 더 좋음.

방법 2: 플랫폼 설정
방법 3: 대체 이미지 사용

이렇게 방법이 있지만 저는 맥북이 요즘 너무 렉이 심해서 그냥 윈도우 환경에서 진행하겠습니다.

이해하기 쉽게 static과 non-static ?
간단히 생각하면, 직렬화 불가능한 필드를 들고 있는 직렬화 가능한 클래스가 되면서 SonarQube가 경고를 낸 거라서 직렬화를 해주면 됩니다.
참고 1, 2,

더 쉽게 검색을 하라면 지금 우측 하단에 보시면 빨간색 Maintainablitiy 우측에 java:S1948이 있는데 이것을 검색하면 스택오버플로우 등 다양한 자료를 확인 할 수 있어 유용합니다.


재 부팅 후, docke up

왜 Exited (137)인가?
Exited (137)은 시스템에 의해 강제 종료됨을 의미 (예: 메모리 부족, 수동 강제종료 등)

과거 실행되고 있었으나 docker-compose에 의해 관리되지 않아 따로 종료되지 않고 남아 있는 것

재 부팅하고 키바나에서 테스트하려 했는데, 이렇게 뜸.
Kibana만 docker-compose.local.yml에 포함되어 있지 않기 때문에 down 명령어의 대상이 아니었고, 따라서 지워지지 않고 남아 있는 것 같은데..흠.

  1. docker-compose -f docker-compose.local.yml down 은 local.yml에 정의된 서비스만 정리함
  2. kibana는 local.yml에 없으므로 제외됨
  3. 그래서 여전히 docker ps -a에 Exited 상태로 살아있는 중

방법 1

컨테이너 직접 제거
docker rm kibana
이렇게 하면 dangling(고아) 상태의 Kibana 컨테이너가 삭제됩니다.

방법 2

Kibana를 docker-compose.local.yml에 추가한 다음 함께 관리
(제 프로젝트는 배포되었으므로 함부러 yml 수정 불가능해서 추후 팀원과 상의 예정)

version: "3.9"

services:
  mysql:
    image: mysql:latest
    container_name: mysql-container
    ports:
      - "3307:3306"
    environment:
      - MYSQL_ROOT_USERNAME=root
      - MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
      - MYSQL_DATABASE=testdb
    networks:
      - local-test

  elastic-search:
    container_name: elasticsearch
    build:
      context: .
      dockerfile: Dockerfile.elastic
    ports:
      - "9200:9200"
    networks:
      - local-test

  kibana:
    image: kibana:8.18.0
    container_name: kibana
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elastic-search
    networks:
      - local-test

  elastic-logstash:
    container_name: logstash-container
    image: logstash:8.18.0
    ports:
      - "5044:5044"
    networks:
      - local-test

  redis:
    container_name: redis-container
    image: redis
    networks:
      - local-test

  spring:
    container_name: spring-container
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      - SPRING_PROFILE=dev
    env_file:
      - .env
    ports:
      - "8080:8080"
    networks:
      - local-test

networks:
  local-test:
    driver: bridge

?? 엔티티 확인해보니까 SELECT * FROM refresh_tokens; 로 해야했는데 s를 빼먹었음
-> 정상 동작 확인.


User, Auth, refreshToken

@Column(name = "deleted_at")
private LocalDateTime deletedAt;

public void softDelete() {
    this.deletedAt = LocalDateTime.now();
}

public boolean isDeleted() {
    return this.deletedAt != null;
}

혼자 드는 생각.
Book은 soft delete 대상이 아닌데, BaseEntity에 deleted 필드를 올리면 Book에도 생기게 되는데, 과연 그게 좋은 설계일까 ??

판단 기준

  • 기준 | 설명
  • 모든 Entity가 soft delete 대상이다 → BaseEntity.deleted로 추상화하는 게 좋음
  • soft delete가 특정 도메인에만 필요하다 → deleted는 각 Entity에 개별로 선언하는 게 낫다

💡 지금 나의 상황

  • Book: 공공 데이터 기반, 삭제 거의 없음 → ❌soft delete 하면 안됨.❌
  • User, Auth, Order, Review: 사용자 활동 기반 → soft delete O
  • BaseEntity: 모든 Entity가 상속 중 → 삭제 필요 없는 Book까지 deleted 필드 생김

→ 따라서 지금은 BaseEntity에 deleted를 넣지 않는 게 맞는 것 같음.

결론
항목 선택
deleted를 BaseEntity로 올리기 → ❌ 지금은 부적절 (Book 등 예외 존재)❌
deleted를 Entity별로 개별 선언 → 현 상황에 가장 안전하고 깔끔한 설계라고 판단.


따라서 필요한 도메인에 개별로 선언해서 사용할 예정.

Order. Review 도메인 soft delete 적용.

Entity, Repository, Service, Controller 수정 -> Test 코드 수정

Order 도메인 수정했는데, 밤샘 이슈로 깜빡하고 재빌드를 안해줌..

  1. 터미널 열고 프로젝트 루트에서 아래 명령어 실행
./gradlew clean compileQuerydsl
  1. 또는 인텔리제이에서
  • Gradle 탭 열기 (오른쪽 사이드바)
  • Tasks > build > compileQuerydsl 더블클릭해서 실행

build.gradle 일부 수정으로 정상 동작 확인.

이젠 review 수정해줬고, 역시 Qclass 반영 안됨.
이 오류는 정확히 말하면 QReview 클래스가 deletedAt 필드를 모르고 있다는 거.
즉, Review 엔티티에는 deletedAt이 분명히 선언되어 있지만, QueryDSL의 Q클래스를 regenerate 하지 않아서 QReview에 반영이 안 된 거.

해결 방법

Q클래스를 재생성하기.

1. 먼저 clean

./gradlew clean
  1. 그리고 compileQuerydsl 실행
./gradlew compileQuerydsl

만약 위 태스크가 안 보인다면, build.gradle에 QueryDSL 설정을 다시 확인해야함.

  1. QReview가 업데이트되었는지 확인

이래도 안되면 ./gradlew clean compileQuerydsl 입력.

원인

  1. IntelliJ가 Q 클래스 변경사항을 인식 못함

  2. 기존 QReview 클래스가 오래된 상태로 캐시됨

  3. QueryDSL이 Review 클래스의 변경을 감지 못하고 다시 생성 안 함

해결법 (지난번과 동일)

  1. build/generated/querydsl 디렉토리 삭제 (Q파일 삭제) (개인적으로 맥북은 이게 최강)
rm -rf ./build/generated/querydsl

또는 IntelliJ에서 수동으로 Q파일 삭제

  1. Gradle clean + build 실행
./gradlew clean build

build만 해도 Q 클래스가 재생성됨. compileQuerydsl 없어도 됨 (우리는 JavaCompile에 annotationProcessorDirectory 설정해놨기 때문)


이미지 업로드 CDN + S3 적용으로 인한 S3 코드 변경

CDN을 도입해서 이미지 로딩 속도를 빠르게 하려는 목적이고, 이를 위해

  • S3 URL 대신 CDN 도메인으로 이미지 접근
  • 따라서, 기존의 s3Client.utilities().getUrl() 반환 방식은 수정해야 함

현재 구조 정리
S3Upload 클래스에서 이미지 업로드 후 S3 URL을 반환 중
s3Client.utilities().getUrl(...).toString()

개선 목표
CDN 도입 시, 사용자는 https://cdn.example.com/profile/UUID_filename.png 처럼 접속해야 하므로

📌 변경 전

return s3Client.utilities().getUrl(GetUrlRequest.builder()
    .bucket(bucket)
    .key(fileName)
    .build()).toString();

📌 변경 후
.yml에 CDN 도메인 추가 후, 아래처럼 수정

  1. application.yml 또는 .env
cloud:
  aws:
    s3:
      bucket: your-bucket-name
      cdn-url: https://cdn.example.com  # 추가
  1. S3Upload 클래스 수정
@Value("${cloud.aws.s3.cdn-url}")
private String cdnUrl;

...

return cdnUrl + "/" + fileName;

이렇게 하면 반환되는 URL이 S3 자체 링크가 아닌 CDN 경유 링크가 된다.

예시로 바뀌는 URL

profile
정진, "어제보다 더 나은 오늘이 되자"

0개의 댓글