Skip to content

Conversation

@uykm
Copy link
Collaborator

@uykm uykm commented May 29, 2025

📝 Work Description

⭐️ 필수 과제

  • 댓글 기능 추가
- 게시글에 댓글을 작성, 수정, 삭제, 조회할 수 있어야 합니다.
- 댓글은 300자 이내로 제한되며, 게시글 상세 조회 시 함께 내려갑니다.
- 댓글 작성자는 유저 정보와 연결되어야 합니다.
  • 좋아요 기능 추가
- 사용자는 게시글과 댓글에 좋아요를 누를 수 있습니다.
  • 페이지네이션 적용
- 게시글 전체 조회 시, 10개 단위로 페이지네이션을 적용해주세요.
- 최신 게시글이 먼저 조회되도록 정렬 기준을 명확히 설정해주세요.
- 페이지 정보와 함께 응답을 구성해주세요. (총 페이지 수, 현재 페이지 등)

🥇 선택 과제

  • 검색 기능 (QueryDSL 기반)
- 게시글 제목과 작성자(닉네임)를 기준으로 검색이 가능해야 합니다.
- 검색어를 포함하는 방식 으로 필터링해주세요.
- JPA만으로 구현해도 되지만, QueryDSL을 학습하고 적용해보면 더 좋습니다.
  • 태그 기능 확장
- 게시글 작성 시 최대 2개의 태그를 선택할 수 있게 합니다.
- 태그는 제한된 집합(서버, 데이터베이스, 인프라, 기타) 중 선택합니다.
- 태그 기반 필터링 및 검색 기능도 함께 제공해주세요
  • 대용량 데이터 처리 및 정렬 최적화
- 게시글이 많아졌을 때 성능이 어떻게 달라지는지 시뮬레이션 해봅니다.
- 인덱스 를 어디에 걸어야 효과적인지 고민해봅시다.
  • 병렬 처리 & 비동기화
- 댓글 리스트 조회, 좋아요 수 조회 등을 비동기/병렬로 처리해보세요.
- CompletableFuture, ExecutorService 등을 사용하여 API 응답을 최적화합니다.
- 병렬 처리 시 발생할 수 있는 동시성 문제, 예외 흐름 등도 함께 고려해봅시다.
  • 캐싱으로 성능 최적화
- 조회가 많은 API(게시글 상세, 전체 목록 등)에 Redis를 활용한 캐싱을 적용합니다.
- 캐시의 key 전략, TTL(Time to Live) 설정, 무효화 정책 등도 함께 고민해주세요.
- 단순히 빨라지는 것만이 아니라, 어떤 트레이드오프가 있는지도 고민해보세요.
  • 인프라 설계 시각화
- 지금 여러분이 개발 중인 서비스를 실제 배포 환경 기준으로 한 번 그려보세요.
- VPC, Subnet, EC2, RDS, S3 등 클라우드 인프라를 활용한 배포 구조를 시각화해봅니다.
- 현재 구조 + 향후 확장 구조(예: WAS 2개, DB 분리 등)를 함께 구성해보세요.
  • JWT 발급 및 검증 로직 구현
- 설계 가이드에 맞춰 JWT 발급 및 검증 로직 구현해봅시다 ㅎㅎ.
- 구현 방법은 완전히 자유입니다. 코드를 찾아서 만들기 보다는, 인증의 전체적인 흐름을 하나하나 적용해보며 구현해봅시다.
  • 로그인 API 리팩터링
- 기존 단순했던 로그인 API를 리팩터링 해봅시다.
- userId 만 넘겨줬다면 이젠 토큰을 넘겨줘야겠죠 ?
- JWT 발급 로직이 구현됐다면, 이후는 수월할거에요.

💡 키워드 과제

  • 리버시 프록시란 무엇인가요 ? (🔗 관련 링크)
  • 쿠키 vs 웹 스토리지 간단히 (🔗 관련 링크)
  • OAuth 란 무엇이고 어떤 이유에 의해 필요하게 되었을까요 ? (🔗 관련 링크)

🔥 이런 고민들이 있습니다!

댓글 기능💡
게시글 상세 조회 시 댓글 목록과 각 댓글의 좋아요 수, 좋아요 여부까지 함께 응답해야 하는 상황인데, PostService가 댓글, 좋아요 수, 그리고 좋아요 여부까지 함께 처리하기엔 도메인 책임이 불분명해진다는 생각이 들었습니다. 그래서, 도메인 서비스는 핵심 비즈니스 로직에 집중시키고, 다수의 도메인을 엮는 조회는 전용 클래스로 분리하는 것도 괜찮겠다는 생각이 들어서 `PostQueryService`라는 것을 따로 만들게 되었습니다. 사실 이렇게 되면 Post 조회 관련 비즈니스 로직들은 모두 `PostQueryService`로 옮겨줘야 하는 것인지 고민이 있었는데, 이렇게 비즈니스 로직을 따로 분리해본 경험이 없어서 더 좋은 팁이 있을지 궁금합니다..

좋아요 기능💡
좋아요 기능을 구현하면서 redis를 사용했습니다. 좋아요 기능은 트래픽이 아주 많이 발생하는 기능들 중 하나라고 생각하는데, 요청 시마다 매번 rdb에 직접 접근하게 될 경우에 조회 성능 저하 및 db 부하가 발생할 것이라 생각했습니다. 좋아요 수는 매초마다 정확한 값을 업데이트해서 보여주기보단 부하를 줄이는 것이 적절한 트레이드오프 전략이라고 생각이 들었고, 그래서 좋아요 수는 redis에 저장하고 일정 주기마다 db로 flush하는 전략을 택했습니다. 좋아요 여부 또한 매번 db에 flush할 필요없이 redis에
게시글과 댓글은 좋아요 처리 방식이 유사하지만, Redis key 형식과 저장 방식이 다릅니다. 하지만, LikeService에서 분기 처리를 해주는 것보단 PostLikeCacheStrategy, CommentLikeCacheStrategy로 분리하는 것이 더 확장성을 보장해준다고 생각해서 Strategy 패턴을 구현해보게 됐습니다.
그리고 캐시 처리 로직을 global 패키지에 둬야 할지, like 도메인 패키지에 둬야 할지 고민을 했었습니다. 좋아요 캐시는 도메인 로직에 깊이 결합되어 있기 때문에 범용성이 없다고 판단이 돼서, 좋아요 관련 캐시 처리 로직은 like 도메인 패키지에 분리하게 됐습니다.

페이지네이션💡
사실 페이지네이션을 이번에 처음 구현해보게 돼서 알맞게 구현한 것인지를 잘 모르겠지만! 일단 단순히 데이터를 나누는 것 말고도, 클라이언트가 이후 요청 흐름을 고려할 수 있도록 총 페이지 수, 현재 페이지, 전체 요소 수, 다음 페이지 유무까지 응답에 포함시키게 됐습니다.

검색 기능 - QueryDSL(예정) ❌
내용

태그 기능 확장💡
다대다 관계를 jpa 연관관계로 풀 수도 있지만, 단순 enum 값만 가지는 태그 구조에서 PostTag를 별도 엔티티로 분리할 필요까지는 없을 것 같다는 생각이 들었습니다. 그래서 ElementCollection + Enum을 이용해 post_tags 라는 중간 테이블을 생성하도록 설계했습니다.
그리고, 태그 필터링 성능 향상을 위해 post_tags.tag 컬럼에 인덱스를 추가했습니다. 태그는 enum 기반으로 고정된 값 집합이라 cardinality가 낮긴 하지만, 검색 조건으로 자주 사용되며 특정 태그에 수천 개 게시글이 몰릴 수 있다는 생각으로 인덱스를 설정하게 됐습니다. 인덱스는 조회 성능을 높여주는 대신, 쓰기 성능을 약간 저하시킬 수 있지만, 쓰기 비용 증가보다 조회 성능 향상이 중요한 구조로 판단했습니다. 따라서, 인덱스를 통해 `Full Scan` 없이 tag 조건을 만족하는 row의 인덱스만 먼저 훑고, 빠르게 결과를 조회할 수 있도록 설계했습니다.

병렬 처리 & 비동기화 ❌
내용

JWT 토큰 기반 로그인 기능💡
이번에 어쩌다보니 sdk 기반 소셜(카카오) 로그인까지 구현하게 됐는데, 이번 앱잼 코드를 미리 작성해본다는 생각으로 구현했던 것 같습니다.

1️⃣ Refresh Token을 DB에서 따로 관리해주게 된 이유 - stateless한 jwt를 서버에서 무효화할 방법은 없지만, db에서 따로 관리해주게 되면 db에서 삭제함으로써 무효화가 가능하게끔 구현했습니다. - 클라이언트가 토큰을 삭제한다고 해도 토큰 자체는 여전히 유효하므로 탈취당한 토큰이라면 만료까지 계속 사용 가능하게 됩니다. 그래서 로그아웃을 했다면 refresh token으로 더이상 재발급이 불가능하도록, 즉 진짜 로그아웃이 가능하도록 하고 싶었습니다. - 다중 기기에서 로그인 했을 경우 하나의 기기에서만 로그인이 될 수 있도록 하는 기능도 생각했는데, 다른 기기에서 로그인을 시도하면 기존 토큰(리프레쉬)을 삭제하고 새로운 토큰을 저장하게 됩니다. 그럼 기존에 로그인 돼있던 핸드폰에서 refresh token으로 재발급이 불가능하게 되고, access Token이 만료됐을 때 로그인이 차단되도록 했습니다. (근데, 카카오톡 같은 걸 생각해보면 다른 기기에서 로그인 시에 즉시 차단되도록 했는데, 아마 redis를 이용해서 매 api 요청마다 체크하도록 하는 것이 아닐까? 라는 생각이 들었어요.)

2️⃣ `@ExtractRefreshToken`을 만든 이유 - Access Token도 헤더에서 추출하기 때문에, 토큰 재발급 시에 Refresh Token도 헤더에서 추출하는게 더 적절하겠다는 생각에 따로 추출할 수 있는 어노테이션을 만들었는데, 사실 코드 통일성만 높였다 느낌이지, 코드가 더 늘기만 한 느낌이라 잘 모르겠습니다 ㅎㅎ..

3️⃣ 패키지 구조를 어떻게 해야할지 고민이 정말 많습니다.. - 일단은 JWT를 생성하거나 파싱하는 과정과 관련된 코드들은 `global/security`에 두고, 토큰 관리와 같은 책임을 담당하는 코드들은 auth 패키지에 뒀는데, 깔끔하다는 생각은 안드네요.. OAuth도 마찬가지로 구조를 어떻게 설계해야할지 감이 잘 안잡혀서.. 혹시 더 좋은 방법을 알고 계시다면 공유좀 부탁드립니다!

4️⃣ 예외 처리 전략 - 인증이 필요한, 즉 SecurityFilterChain을 거치는 과정에서의 예외는 필터(JwtExceptionFiter)에서 처리하도록 했고, 이외에 필터를 거치지 않거나 이미 거친 이후에 비즈니스 로직 흐름 속에서 JWT 검증 및 파싱 과정에서 발생하는 예외는 GlobalExceptionHandler에서 잡게끔 설계했습니다.

5️⃣ 소셜 로그인 구현 과정에서의 확장성 고려 - 각 클래스가 맡고 있는 책임을 간단하게 정리해놨는데, 이후 구글, 네이버, 애플과 같은 로그인도 구현하게 될 경우 `OAuthService`에서 모든 것을 처리하게 됐을 때 책임 분리도 불분명해지고 재사용성 및 확장성도 떨어지게 될 것이라는 생각이 들어서 이렇게 설계해보게 됐습니다.
AuthService: 토큰 발급/재발급/로그아웃 (인증 흐름 제어) OAuthServiceProvider: 소셜 플랫폼 라우팅 역할 KakaoOAuthServiceImpl: 카카오 API 통신 SocialUserProvider: 사용자 생성/연동 로직 (도메인 로직)


📷 Screenshot

@uykm uykm requested review from Jang99u, PBEM22, choyeongju, kaswhy, kyoooooong and rootTiket and removed request for rootTiket June 27, 2025 05:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants